#include "Enet.h"
#include <time.h>

#pragma warning (disable: 4996) // strncpy
#pragma warning (disable: 4146)

namespace ENet
{
	static ENetCallbacks callbacks = { malloc, free, abort };

	extern void enet_list_clear (ENetList *);

	extern ENetListIterator enet_list_insert (ENetListIterator, void *);
	extern void * enet_list_remove (ENetListIterator);
	extern ENetListIterator enet_list_move (ENetListIterator, void *, void *);

	extern size_t enet_list_size (ENetList *);

	int
		enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits)
	{
		if (version < ENET_VERSION_CREATE (1, 3, 0))
			return -1;

		if (inits -> malloc != NULL || inits -> free != NULL)
		{
			if (inits -> malloc == NULL || inits -> free == NULL)
				return -1;

			callbacks.malloc = inits -> malloc;
			callbacks.free = inits -> free;
		}

		if (inits -> no_memory != NULL)
			callbacks.no_memory = inits -> no_memory;

		return enet_initialize ();
	}

	void *
		enet_malloc (size_t size)
	{
		void * memory = callbacks.malloc (size);

		if (memory == NULL)
			callbacks.no_memory ();

		return memory;
	}

	void
		enet_free (void * memory)
	{
		callbacks.free (memory);
	}


	typedef struct _ENetSymbol
	{
		/* binary indexed tree of symbols */
		enet_uint8 value;
		enet_uint8 count;
		enet_uint16 under;
		enet_uint16 left, right;

		/* context defined by this symbol */
		enet_uint16 symbols;
		enet_uint16 escapes;
		enet_uint16 total;
		enet_uint16 parent; 
	} ENetSymbol;

	/* adaptation constants tuned aggressively for small packet sizes rather than large file compression */
	enum
	{
		ENET_RANGE_CODER_TOP    = 1<<24,
		ENET_RANGE_CODER_BOTTOM = 1<<16,

		ENET_CONTEXT_SYMBOL_DELTA = 3,
		ENET_CONTEXT_SYMBOL_MINIMUM = 1,
		ENET_CONTEXT_ESCAPE_MINIMUM = 1,

		ENET_SUBCONTEXT_ORDER = 2,
		ENET_SUBCONTEXT_SYMBOL_DELTA = 2,
		ENET_SUBCONTEXT_ESCAPE_DELTA = 5
	};

	/* context exclusion roughly halves compression speed, so disable for now */
#undef ENET_CONTEXT_EXCLUSION

	typedef struct _ENetRangeCoder
	{
		/* only allocate enough symbols for reasonable MTUs, would need to be larger for large file compression */
		ENetSymbol symbols[4096];
	} ENetRangeCoder;

	void *
		enet_range_coder_create (void)
	{
		ENetRangeCoder * rangeCoder = (ENetRangeCoder *) enet_malloc (sizeof (ENetRangeCoder));
		if (rangeCoder == NULL)
			return NULL;

		return rangeCoder;
	}

	void
		enet_range_coder_destroy (void * context)
	{
		ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context;
		if (rangeCoder == NULL)
			return;

		enet_free (rangeCoder);
	}

#define ENET_SYMBOL_CREATE(symbol, value_, count_) \
	{ \
	symbol = & rangeCoder -> symbols [nextSymbol ++]; \
	symbol -> value = value_; \
	symbol -> count = count_; \
	symbol -> under = count_; \
	symbol -> left = 0; \
	symbol -> right = 0; \
	symbol -> symbols = 0; \
	symbol -> escapes = 0; \
	symbol -> total = 0; \
	symbol -> parent = 0; \
	}

#define ENET_CONTEXT_CREATE(context, escapes_, minimum) \
	{ \
	ENET_SYMBOL_CREATE (context, 0, 0); \
	(context) -> escapes = escapes_; \
	(context) -> total = escapes_ + 256*minimum; \
	(context) -> symbols = 0; \
	}

	static enet_uint16
		enet_symbol_rescale (ENetSymbol * symbol)
	{
		enet_uint16 total = 0;
		for (;;)
		{
			symbol -> count -= symbol->count >> 1;
			symbol -> under = symbol -> count;
			if (symbol -> left)
				symbol -> under += enet_symbol_rescale (symbol + symbol -> left);
			total += symbol -> under;
			if (! symbol -> right) break;
			symbol += symbol -> right;
		} 
		return total;
	}

#define ENET_CONTEXT_RESCALE(context, minimum) \
	{ \
	(context) -> total = (context) -> symbols ? enet_symbol_rescale ((context) + (context) -> symbols) : 0; \
	(context) -> escapes -= (context) -> escapes >> 1; \
	(context) -> total += (context) -> escapes + 256*minimum; \
	}

#define ENET_RANGE_CODER_OUTPUT(value) \
	{ \
	if (outData >= outEnd) \
	return 0; \
	* outData ++ = value; \
	}

#define ENET_RANGE_CODER_ENCODE(under, count, total) \
	{ \
	encodeRange /= (total); \
	encodeLow += (under) * encodeRange; \
	encodeRange *= (count); \
	for (;;) \
	{ \
	if((encodeLow ^ (encodeLow + encodeRange)) >= ENET_RANGE_CODER_TOP) \
	{ \
	if(encodeRange >= ENET_RANGE_CODER_BOTTOM) break; \
	encodeRange = -encodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \
	} \
	ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \
	encodeRange <<= 8; \
	encodeLow <<= 8; \
	} \
	}

#define ENET_RANGE_CODER_FLUSH \
	{ \
	while (encodeLow) \
	{ \
	ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \
	encodeLow <<= 8; \
	} \
	}

#define ENET_RANGE_CODER_FREE_SYMBOLS \
	{ \
	if (nextSymbol >= sizeof (rangeCoder -> symbols) / sizeof (ENetSymbol) - ENET_SUBCONTEXT_ORDER ) \
	{ \
	nextSymbol = 0; \
	ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); \
	predicted = 0; \
	order = 0; \
	} \
	}

#define ENET_CONTEXT_ENCODE(context, symbol_, value_, under_, count_, update, minimum) \
	{ \
	under_ = value*minimum; \
	count_ = minimum; \
	if (! (context) -> symbols) \
	{ \
	ENET_SYMBOL_CREATE (symbol_, value_, update); \
	(context) -> symbols = symbol_ - (context); \
	} \
	else \
	{ \
	ENetSymbol * node = (context) + (context) -> symbols; \
	for (;;) \
	{ \
	if (value_ < node -> value) \
	{ \
	node -> under += update; \
	if (node -> left) { node += node -> left; continue; } \
	ENET_SYMBOL_CREATE (symbol_, value_, update); \
	node -> left = symbol_ - node; \
	} \
			else \
			if (value_ > node -> value) \
	{ \
	under_ += node -> under; \
	if (node -> right) { node += node -> right; continue; } \
	ENET_SYMBOL_CREATE (symbol_, value_, update); \
	node -> right = symbol_ - node; \
	} \
			else \
	{ \
	count_ += node -> count; \
	under_ += node -> under - node -> count; \
	node -> under += update; \
	node -> count += update; \
	symbol_ = node; \
	} \
	break; \
	} \
	} \
	}

#ifdef ENET_CONTEXT_EXCLUSION
	static const ENetSymbol emptyContext = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

#define ENET_CONTEXT_WALK(context, body) \
	{ \
	const ENetSymbol * node = (context) + (context) -> symbols; \
	const ENetSymbol * stack [256]; \
	size_t stackSize = 0; \
	while (node -> left) \
	{ \
	stack [stackSize ++] = node; \
	node += node -> left; \
	} \
	for (;;) \
	{ \
	body; \
	if (node -> right) \
	{ \
	node += node -> right; \
	while (node -> left) \
	{ \
	stack [stackSize ++] = node; \
	node += node -> left; \
	} \
	} \
		else \
		if (stackSize <= 0) \
		break; \
		else \
		node = stack [-- stackSize]; \
	} \
	}

#define ENET_CONTEXT_ENCODE_EXCLUDE(context, value_, under, total, minimum) \
	ENET_CONTEXT_WALK(context, { \
	if (node -> value != value_) \
	{ \
	enet_uint16 parentCount = rangeCoder -> symbols [node -> parent].count + minimum; \
	if (node -> value < value_) \
	under -= parentCount; \
	total -= parentCount; \
	} \
	})
#endif

	size_t
		enet_range_coder_compress (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit)
	{
		ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context;
		enet_uint8 * outStart = outData, * outEnd = & outData [outLimit];
		const enet_uint8 * inData, * inEnd;
		enet_uint32 encodeLow = 0, encodeRange = ~0;
		ENetSymbol * root;
		enet_uint16 predicted = 0;
		size_t order = 0, nextSymbol = 0;

		if (rangeCoder == NULL || inBufferCount <= 0 || inLimit <= 0)
			return 0;

		inData = (const enet_uint8 *) inBuffers -> data;
		inEnd = & inData [inBuffers -> dataLength];
		inBuffers ++;
		inBufferCount --;

		ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM);

		for (;;)
		{
			ENetSymbol * subcontext, * symbol;
#ifdef ENET_CONTEXT_EXCLUSION
			const ENetSymbol * childContext = & emptyContext;
#endif
			enet_uint8 value;
			enet_uint16 count, under, * parent = & predicted, total;
			if (inData >= inEnd)
			{
				if (inBufferCount <= 0)
					break;
				inData = (const enet_uint8 *) inBuffers -> data;
				inEnd = & inData [inBuffers -> dataLength];
				inBuffers ++;
				inBufferCount --;
			}
			value = * inData ++;

			for (subcontext = & rangeCoder -> symbols [predicted]; 
				subcontext != root; 
#ifdef ENET_CONTEXT_EXCLUSION
				childContext = subcontext, 
#endif
				subcontext = & rangeCoder -> symbols [subcontext -> parent])
			{
				ENET_CONTEXT_ENCODE (subcontext, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0);
				* parent = symbol - rangeCoder -> symbols;
				parent = & symbol -> parent;
				total = subcontext -> total;
#ifdef ENET_CONTEXT_EXCLUSION
				if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA)
					ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, 0);
#endif
				if (count > 0)
				{
					ENET_RANGE_CODER_ENCODE (subcontext -> escapes + under, count, total);
				}
				else
				{
					if (subcontext -> escapes > 0 && subcontext -> escapes < total) 
						ENET_RANGE_CODER_ENCODE (0, subcontext -> escapes, total); 
					subcontext -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA;
					subcontext -> total += ENET_SUBCONTEXT_ESCAPE_DELTA;
				}
				subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA;
				if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
					ENET_CONTEXT_RESCALE (subcontext, 0);
				if (count > 0) goto nextInput;
			}

			ENET_CONTEXT_ENCODE (root, symbol, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM);
			* parent = symbol - rangeCoder -> symbols;
			parent = & symbol -> parent;
			total = root -> total;
#ifdef ENET_CONTEXT_EXCLUSION
			if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA)
				ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, ENET_CONTEXT_SYMBOL_MINIMUM); 
#endif
			ENET_RANGE_CODER_ENCODE (root -> escapes + under, count, total);
			root -> total += ENET_CONTEXT_SYMBOL_DELTA; 
			if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
				ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM);

nextInput:
			if (order >= ENET_SUBCONTEXT_ORDER) 
				predicted = rangeCoder -> symbols [predicted].parent;
			else 
				order ++;
			ENET_RANGE_CODER_FREE_SYMBOLS;
		}

		ENET_RANGE_CODER_FLUSH;

		return (size_t) (outData - outStart);
	}

#define ENET_RANGE_CODER_SEED \
	{ \
	if (inData < inEnd) decodeCode |= * inData ++ << 24; \
	if (inData < inEnd) decodeCode |= * inData ++ << 16; \
	if (inData < inEnd) decodeCode |= * inData ++ << 8; \
	if (inData < inEnd) decodeCode |= * inData ++; \
	}

#define ENET_RANGE_CODER_READ(total) ((decodeCode - decodeLow) / (decodeRange /= (total)))

#define ENET_RANGE_CODER_DECODE(under, count, total) \
	{ \
	decodeLow += (under) * decodeRange; \
	decodeRange *= (count); \
	for (;;) \
	{ \
	if((decodeLow ^ (decodeLow + decodeRange)) >= ENET_RANGE_CODER_TOP) \
	{ \
	if(decodeRange >= ENET_RANGE_CODER_BOTTOM) break; \
	decodeRange = -decodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \
	} \
	decodeCode <<= 8; \
	if (inData < inEnd) \
	decodeCode |= * inData ++; \
	decodeRange <<= 8; \
	decodeLow <<= 8; \
	} \
	}

#define ENET_CONTEXT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, createRoot, visitNode, createRight, createLeft) \
	{ \
	under_ = 0; \
	count_ = minimum; \
	if (! (context) -> symbols) \
	{ \
	createRoot; \
	} \
	else \
	{ \
	ENetSymbol * node = (context) + (context) -> symbols; \
	for (;;) \
	{ \
	enet_uint16 after = under_ + node -> under + (node -> value + 1)*minimum, before = node -> count + minimum; \
	visitNode; \
	if (code >= after) \
	{ \
	under_ += node -> under; \
	if (node -> right) { node += node -> right; continue; } \
	createRight; \
	} \
			else \
			if (code < after - before) \
	{ \
	node -> under += update; \
	if (node -> left) { node += node -> left; continue; } \
	createLeft; \
	} \
			else \
	{ \
	value_ = node -> value; \
	count_ += node -> count; \
	under_ = after - before; \
	node -> under += update; \
	node -> count += update; \
	symbol_ = node; \
	} \
	break; \
	} \
	} \
	}

#define ENET_CONTEXT_TRY_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \
	ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, return 0, exclude (node -> value, after, before), return 0, return 0)

#define ENET_CONTEXT_ROOT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \
	ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, \
	{ \
	value_ = code / minimum; \
	under_ = code - code%minimum; \
	ENET_SYMBOL_CREATE (symbol_, value_, update); \
	(context) -> symbols = symbol_ - (context); \
	}, \
	exclude (node -> value, after, before), \
	{ \
	value_ = node->value + 1 + (code - after)/minimum; \
	under_ = code - (code - after)%minimum; \
	ENET_SYMBOL_CREATE (symbol_, value_, update); \
	node -> right = symbol_ - node; \
	}, \
	{ \
	value_ = node->value - 1 - (after - before - code - 1)/minimum; \
	under_ = code - (after - before - code - 1)%minimum; \
	ENET_SYMBOL_CREATE (symbol_, value_, update); \
	node -> left = symbol_ - node; \
	}) \

#ifdef ENET_CONTEXT_EXCLUSION
	typedef struct _ENetExclude
	{
		enet_uint8 value;
		enet_uint16 under;
	} ENetExclude;

#define ENET_CONTEXT_DECODE_EXCLUDE(context, total, minimum) \
	{ \
	enet_uint16 under = 0; \
	nextExclude = excludes; \
	ENET_CONTEXT_WALK (context, { \
	under += rangeCoder -> symbols [node -> parent].count + minimum; \
	nextExclude -> value = node -> value; \
	nextExclude -> under = under; \
	nextExclude ++; \
	}); \
	total -= under; \
	}

#define ENET_CONTEXT_EXCLUDED(value_, after, before) \
	{ \
	size_t low = 0, high = nextExclude - excludes; \
	for(;;) \
	{ \
	size_t mid = (low + high) >> 1; \
	const ENetExclude * exclude = & excludes [mid]; \
	if (value_ < exclude -> value) \
	{ \
	if (low + 1 < high) \
	{ \
	high = mid; \
	continue; \
	} \
	if (exclude > excludes) \
	after -= exclude [-1].under; \
	} \
		else \
	{ \
	if (value_ > exclude -> value) \
	{ \
	if (low + 1 < high) \
	{ \
	low = mid; \
	continue; \
	} \
	} \
			else \
			before = 0; \
			after -= exclude -> under; \
	} \
	break; \
	} \
	}
#endif

#define ENET_CONTEXT_NOT_EXCLUDED(value_, after, before)

	size_t
		enet_range_coder_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit)
	{
		ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context;
		enet_uint8 * outStart = outData, * outEnd = & outData [outLimit];
		const enet_uint8 * inEnd = & inData [inLimit];
		enet_uint32 decodeLow = 0, decodeCode = 0, decodeRange = ~0;
		ENetSymbol * root;
		enet_uint16 predicted = 0;
		size_t order = 0, nextSymbol = 0;
#ifdef ENET_CONTEXT_EXCLUSION
		ENetExclude excludes [256];
		ENetExclude * nextExclude = excludes;
#endif

		if (rangeCoder == NULL || inLimit <= 0)
			return 0;

		ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM);

		ENET_RANGE_CODER_SEED;

		for (;;)
		{
			ENetSymbol * subcontext, * symbol, * patch;
#ifdef ENET_CONTEXT_EXCLUSION
			const ENetSymbol * childContext = & emptyContext;
#endif
			enet_uint8 value = 0;
			enet_uint16 code, under, count, bottom, * parent = & predicted, total;

			for (subcontext = & rangeCoder -> symbols [predicted];
				subcontext != root;
#ifdef ENET_CONTEXT_EXCLUSION
				childContext = subcontext, 
#endif
				subcontext = & rangeCoder -> symbols [subcontext -> parent])
			{
				if (subcontext -> escapes <= 0)
					continue;
				total = subcontext -> total;
#ifdef ENET_CONTEXT_EXCLUSION
				if (childContext -> total > 0) 
					ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, 0); 
#endif
				if (subcontext -> escapes >= total)
					continue;
				code = ENET_RANGE_CODER_READ (total);
				if (code < subcontext -> escapes) 
				{
					ENET_RANGE_CODER_DECODE (0, subcontext -> escapes, total); 
					continue;
				}
				code -= subcontext -> escapes;
#ifdef ENET_CONTEXT_EXCLUSION
				if (childContext -> total > 0)
				{
					ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_EXCLUDED); 
				}
				else
#endif
				{
					ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_NOT_EXCLUDED); 
				}
				bottom = symbol - rangeCoder -> symbols;
				ENET_RANGE_CODER_DECODE (subcontext -> escapes + under, count, total);
				subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA;
				if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
					ENET_CONTEXT_RESCALE (subcontext, 0);
				goto patchContexts;
			}

			total = root -> total;
#ifdef ENET_CONTEXT_EXCLUSION
			if (childContext -> total > 0)
				ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, ENET_CONTEXT_SYMBOL_MINIMUM);  
#endif
			code = ENET_RANGE_CODER_READ (total);
			if (code < root -> escapes)
			{
				ENET_RANGE_CODER_DECODE (0, root -> escapes, total);
				break;
			}
			code -= root -> escapes;
#ifdef ENET_CONTEXT_EXCLUSION
			if (childContext -> total > 0)
			{
				ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_EXCLUDED); 
			}
			else
#endif
			{
				ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_NOT_EXCLUDED); 
			}
			bottom = symbol - rangeCoder -> symbols;
			ENET_RANGE_CODER_DECODE (root -> escapes + under, count, total);
			root -> total += ENET_CONTEXT_SYMBOL_DELTA;
			if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
				ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM);

patchContexts:
			for (patch = & rangeCoder -> symbols [predicted];
				patch != subcontext;
				patch = & rangeCoder -> symbols [patch -> parent])
			{
				ENET_CONTEXT_ENCODE (patch, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0);
				* parent = symbol - rangeCoder -> symbols;
				parent = & symbol -> parent;
				if (count <= 0)
				{
					patch -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA;
					patch -> total += ENET_SUBCONTEXT_ESCAPE_DELTA;
				}
				patch -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; 
				if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || patch -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
					ENET_CONTEXT_RESCALE (patch, 0);
			}
			* parent = bottom;

			ENET_RANGE_CODER_OUTPUT (value);

			if (order >= ENET_SUBCONTEXT_ORDER)
				predicted = rangeCoder -> symbols [predicted].parent;
			else
				order ++;
			ENET_RANGE_CODER_FREE_SYMBOLS;
		}

		return (size_t) (outData - outStart);
	}

	/** @defgroup host ENet host functions
	@{
	*/

	/** Sets the packet compressor the host should use to the default range coder.
	@param host host to enable the range coder for
	@returns 0 on success, < 0 on failure
	*/
	int
		enet_host_compress_with_range_coder (ENetHost * host)
	{
		ENetCompressor compressor;
		memset (& compressor, 0, sizeof (compressor));
		compressor.context = enet_range_coder_create();
		if (compressor.context == NULL)
			return -1;
		compressor.compress = enet_range_coder_compress;
		compressor.decompress = enet_range_coder_decompress;
		compressor.destroy = enet_range_coder_destroy;
		enet_host_compress (host, & compressor);
		return 0;
	}


	ENetHost *
		enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth)
	{
		ENetHost * host;
		ENetPeer * currentPeer;

		if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID)
			return NULL;

		host = (ENetHost *) enet_malloc (sizeof (ENetHost));
		if (host == NULL)
			return NULL;

		host -> peers = (ENetPeer *) enet_malloc (peerCount * sizeof (ENetPeer));
		if (host -> peers == NULL)
		{
			enet_free (host);

			return NULL;
		}
		memset (host -> peers, 0, peerCount * sizeof (ENetPeer));

		host -> socket = enet_socket_create (ENET_SOCKET_TYPE_DATAGRAM);
		if (host -> socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind (host -> socket, address) < 0))
		{
			if (host -> socket != ENET_SOCKET_NULL)
				enet_socket_destroy (host -> socket);

			enet_free (host -> peers);
			enet_free (host);

			return NULL;
		}

		enet_socket_set_option (host -> socket, ENET_SOCKOPT_NONBLOCK, 1);
		enet_socket_set_option (host -> socket, ENET_SOCKOPT_BROADCAST, 1);
		enet_socket_set_option (host -> socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE);
		enet_socket_set_option (host -> socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE);

		if (address != NULL)
			host -> address = * address;

		if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
			channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
		else
			if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
				channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;

		host -> randomSeed = (enet_uint32) time(NULL) + (enet_uint32) (size_t) host;
		host -> randomSeed = (host -> randomSeed << 16) | (host -> randomSeed >> 16);
		host -> channelLimit = channelLimit;
		host -> incomingBandwidth = incomingBandwidth;
		host -> outgoingBandwidth = outgoingBandwidth;
		host -> bandwidthThrottleEpoch = 0;
		host -> recalculateBandwidthLimits = 0;
		host -> mtu = ENET_HOST_DEFAULT_MTU;
		host -> peerCount = peerCount;
		host -> commandCount = 0;
		host -> bufferCount = 0;
		host -> checksum = NULL;
		host -> receivedAddress.host = ENET_HOST_ANY;
		host -> receivedAddress.port = 0;
		host -> receivedData = NULL;
		host -> receivedDataLength = 0;

		host -> totalSentData = 0;
		host -> totalSentPackets = 0;
		host -> totalReceivedData = 0;
		host -> totalReceivedPackets = 0;

		host -> compressor.context = NULL;
		host -> compressor.compress = NULL;
		host -> compressor.decompress = NULL;
		host -> compressor.destroy = NULL;

		enet_list_clear (& host -> dispatchQueue);

		for (currentPeer = host -> peers;
			currentPeer < & host -> peers [host -> peerCount];
			++ currentPeer)
		{
			currentPeer -> host = host;
			currentPeer -> incomingPeerID = currentPeer - host -> peers;
			currentPeer -> outgoingSessionID = currentPeer -> incomingSessionID = 0xFF;
			currentPeer -> data = NULL;

			enet_list_clear (& currentPeer -> acknowledgements);
			enet_list_clear (& currentPeer -> sentReliableCommands);
			enet_list_clear (& currentPeer -> sentUnreliableCommands);
			enet_list_clear (& currentPeer -> outgoingReliableCommands);
			enet_list_clear (& currentPeer -> outgoingUnreliableCommands);
			enet_list_clear (& currentPeer -> dispatchedCommands);

			enet_peer_reset (currentPeer);
		}

		return host;
	}

	/** Destroys the host and all resources associated with it.
	@param host pointer to the host to destroy
	*/
	void
		enet_host_destroy (ENetHost * host)
	{
		ENetPeer * currentPeer;

		enet_socket_destroy (host -> socket);

		for (currentPeer = host -> peers;
			currentPeer < & host -> peers [host -> peerCount];
			++ currentPeer)
		{
			enet_peer_reset (currentPeer);
		}

		if (host -> compressor.context != NULL && host -> compressor.destroy)
			(* host -> compressor.destroy) (host -> compressor.context);

		enet_free (host -> peers);
		enet_free (host);
	}

	/** Initiates a connection to a foreign host.
	@param host host seeking the connection
	@param address destination for the connection
	@param channelCount number of channels to allocate
	@param data user data supplied to the receiving host 
	@returns a peer representing the foreign host on success, NULL on failure
	@remarks The peer returned will have not completed the connection until enet_host_service()
	notifies of an ENET_EVENT_TYPE_CONNECT event for the peer.
	*/
	ENetPeer *
		enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelCount, enet_uint32 data)
	{
		ENetPeer * currentPeer;
		ENetChannel * channel;
		ENetProtocol command;

		if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
			channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;
		else
			if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
				channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;

		for (currentPeer = host -> peers;
			currentPeer < & host -> peers [host -> peerCount];
			++ currentPeer)
		{
			if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED)
				break;
		}

		if (currentPeer >= & host -> peers [host -> peerCount])
			return NULL;

		currentPeer -> channels = (ENetChannel *) enet_malloc (channelCount * sizeof (ENetChannel));
		if (currentPeer -> channels == NULL)
			return NULL;
		currentPeer -> channelCount = channelCount;
		currentPeer -> state = ENET_PEER_STATE_CONNECTING;
		currentPeer -> address = * address;
		currentPeer -> connectID = ++ host -> randomSeed;

		if (host -> outgoingBandwidth == 0)
			currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
		else
			currentPeer -> windowSize = (host -> outgoingBandwidth /
			ENET_PEER_WINDOW_SIZE_SCALE) * 
			ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

		if (currentPeer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
			currentPeer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
		else
			if (currentPeer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
				currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

		for (channel = currentPeer -> channels;
			channel < & currentPeer -> channels [channelCount];
			++ channel)
		{
			channel -> outgoingReliableSequenceNumber = 0;
			channel -> outgoingUnreliableSequenceNumber = 0;
			channel -> incomingReliableSequenceNumber = 0;
			channel -> incomingUnreliableSequenceNumber = 0;

			enet_list_clear (& channel -> incomingReliableCommands);
			enet_list_clear (& channel -> incomingUnreliableCommands);

			channel -> usedReliableWindows = 0;
			memset (channel -> reliableWindows, 0, sizeof (channel -> reliableWindows));
		}

		command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
		command.header.channelID = 0xFF;
		command.connect.outgoingPeerID = ENET_HOST_TO_NET_16 (currentPeer -> incomingPeerID);
		command.connect.incomingSessionID = currentPeer -> incomingSessionID;
		command.connect.outgoingSessionID = currentPeer -> outgoingSessionID;
		command.connect.mtu = ENET_HOST_TO_NET_32 (currentPeer -> mtu);
		command.connect.windowSize = ENET_HOST_TO_NET_32 (currentPeer -> windowSize);
		command.connect.channelCount = ENET_HOST_TO_NET_32 (channelCount);
		command.connect.incomingBandwidth = ENET_HOST_TO_NET_32 (host -> incomingBandwidth);
		command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth);
		command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleInterval);
		command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleAcceleration);
		command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleDeceleration);
		command.connect.connectID = currentPeer -> connectID;
		command.connect.data = ENET_HOST_TO_NET_32 (data);

		enet_peer_queue_outgoing_command (currentPeer, & command, NULL, 0, 0);

		return currentPeer;
	}

	/** Queues a packet to be sent to all peers associated with the host.
	@param host host on which to broadcast the packet
	@param channelID channel on which to broadcast
	@param packet packet to broadcast
	*/
	void
		enet_host_broadcast (ENetHost * host, enet_uint8 channelID, ENetPacket * packet)
	{
		ENetPeer * currentPeer;

		for (currentPeer = host -> peers;
			currentPeer < & host -> peers [host -> peerCount];
			++ currentPeer)
		{
			if (currentPeer -> state != ENET_PEER_STATE_CONNECTED)
				continue;

			enet_peer_send (currentPeer, channelID, packet);
		}

		if (packet -> referenceCount == 0)
			enet_packet_destroy (packet);
	}

	/** Sets the packet compressor the host should use to compress and decompress packets.
	@param host host to enable or disable compression for
	@param compressor callbacks for for the packet compressor; if NULL, then compression is disabled
	*/
	void
		enet_host_compress (ENetHost * host, const ENetCompressor * compressor)
	{
		if (host -> compressor.context != NULL && host -> compressor.destroy)
			(* host -> compressor.destroy) (host -> compressor.context);

		if (compressor)
			host -> compressor = * compressor;
		else
			host -> compressor.context = NULL;
	}

	/** Limits the maximum allowed channels of future incoming connections.
	@param host host to limit
	@param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
	*/
	void
		enet_host_channel_limit (ENetHost * host, size_t channelLimit)
	{
		if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
			channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
		else
			if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
				channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;

		host -> channelLimit = channelLimit;
	}


	/** Adjusts the bandwidth limits of a host.
	@param host host to adjust
	@param incomingBandwidth new incoming bandwidth
	@param outgoingBandwidth new outgoing bandwidth
	@remarks the incoming and outgoing bandwidth parameters are identical in function to those
	specified in enet_host_create().
	*/
	void
		enet_host_bandwidth_limit (ENetHost * host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth)
	{
		host -> incomingBandwidth = incomingBandwidth;
		host -> outgoingBandwidth = outgoingBandwidth;
		host -> recalculateBandwidthLimits = 1;
	}

	void
		enet_host_bandwidth_throttle (ENetHost * host)
	{
		enet_uint32 timeCurrent = enet_time_get (),
			elapsedTime = timeCurrent - host -> bandwidthThrottleEpoch,
			peersTotal = 0,
			dataTotal = 0,
			peersRemaining,
			bandwidth,
			throttle = 0,
			bandwidthLimit = 0;
		int needsAdjustment;
		ENetPeer * peer;
		ENetProtocol command;

		if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL)
			return;

		for (peer = host -> peers;
			peer < & host -> peers [host -> peerCount];
			++ peer)
		{
			if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)
				continue;

			++ peersTotal;
			dataTotal += peer -> outgoingDataTotal;
		}

		if (peersTotal == 0)
			return;

		peersRemaining = peersTotal;
		needsAdjustment = 1;

		if (host -> outgoingBandwidth == 0)
			bandwidth = ~0;
		else
			bandwidth = (host -> outgoingBandwidth * elapsedTime) / 1000;

		while (peersRemaining > 0 && needsAdjustment != 0)
		{
			needsAdjustment = 0;

			if (dataTotal < bandwidth)
				throttle = ENET_PEER_PACKET_THROTTLE_SCALE;
			else
				throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal;

			for (peer = host -> peers;
				peer < & host -> peers [host -> peerCount];
				++ peer)
			{
				enet_uint32 peerBandwidth;

				if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) ||
					peer -> incomingBandwidth == 0 ||
					peer -> outgoingBandwidthThrottleEpoch == timeCurrent)
					continue;

				peerBandwidth = (peer -> incomingBandwidth * elapsedTime) / 1000;
				if ((throttle * peer -> outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth)
					continue;

				peer -> packetThrottleLimit = (peerBandwidth * 
					ENET_PEER_PACKET_THROTTLE_SCALE) / peer -> outgoingDataTotal;

				if (peer -> packetThrottleLimit == 0)
					peer -> packetThrottleLimit = 1;

				if (peer -> packetThrottle > peer -> packetThrottleLimit)
					peer -> packetThrottle = peer -> packetThrottleLimit;

				peer -> outgoingBandwidthThrottleEpoch = timeCurrent;


				needsAdjustment = 1;
				-- peersRemaining;
				bandwidth -= peerBandwidth;
				dataTotal -= peerBandwidth;
			}
		}

		if (peersRemaining > 0)
			for (peer = host -> peers;
				peer < & host -> peers [host -> peerCount];
				++ peer)
			{
				if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) ||
					peer -> outgoingBandwidthThrottleEpoch == timeCurrent)
					continue;

				peer -> packetThrottleLimit = throttle;

				if (peer -> packetThrottle > peer -> packetThrottleLimit)
					peer -> packetThrottle = peer -> packetThrottleLimit;
			}

			if (host -> recalculateBandwidthLimits)
			{
				host -> recalculateBandwidthLimits = 0;

				peersRemaining = peersTotal;
				bandwidth = host -> incomingBandwidth;
				needsAdjustment = 1;

				if (bandwidth == 0)
					bandwidthLimit = 0;
				else
					while (peersRemaining > 0 && needsAdjustment != 0)
					{
						needsAdjustment = 0;
						bandwidthLimit = bandwidth / peersRemaining;

						for (peer = host -> peers;
							peer < & host -> peers [host -> peerCount];
							++ peer)
						{
							if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) ||
								peer -> incomingBandwidthThrottleEpoch == timeCurrent)
								continue;

							if (peer -> outgoingBandwidth > 0 &&
								peer -> outgoingBandwidth >= bandwidthLimit)
								continue;

							peer -> incomingBandwidthThrottleEpoch = timeCurrent;

							needsAdjustment = 1;
							-- peersRemaining;
							bandwidth -= peer -> outgoingBandwidth;
						}
					}

					for (peer = host -> peers;
						peer < & host -> peers [host -> peerCount];
						++ peer)
					{
						if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)
							continue;

						command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
						command.header.channelID = 0xFF;
						command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth);

						if (peer -> incomingBandwidthThrottleEpoch == timeCurrent)
							command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (peer -> outgoingBandwidth);
						else
							command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (bandwidthLimit);

						enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0);
					} 
			}

			host -> bandwidthThrottleEpoch = timeCurrent;

			for (peer = host -> peers;
				peer < & host -> peers [host -> peerCount];
				++ peer)
			{
				peer -> incomingDataTotal = 0;
				peer -> outgoingDataTotal = 0;
			}
	}

	void
		enet_list_clear (ENetList * list)
	{
		list -> sentinel.next = & list -> sentinel;
		list -> sentinel.previous = & list -> sentinel;
	}

	ENetListIterator
		enet_list_insert (ENetListIterator position, void * data)
	{
		ENetListIterator result = (ENetListIterator) data;

		result -> previous = position -> previous;
		result -> next = position;

		result -> previous -> next = result;
		position -> previous = result;

		return result;
	}

	void *
		enet_list_remove (ENetListIterator position)
	{
		position -> previous -> next = position -> next;
		position -> next -> previous = position -> previous;

		return position;
	}

	ENetListIterator
		enet_list_move (ENetListIterator position, void * dataFirst, void * dataLast)
	{
		ENetListIterator first = (ENetListIterator) dataFirst,
			last = (ENetListIterator) dataLast;

		first -> previous -> next = last -> next;
		last -> next -> previous = first -> previous;

		first -> previous = position -> previous;
		last -> next = position;

		first -> previous -> next = first;
		position -> previous = last;

		return first;
	}

	size_t
		enet_list_size (ENetList * list)
	{
		size_t size = 0;
		ENetListIterator position;

		for (position = enet_list_begin (list);
			position != enet_list_end (list);
			position = enet_list_next (position))
			++ size;

		return size;
	}

	ENetPacket *
		enet_packet_create (const void * data, size_t dataLength, enet_uint32 flags)
	{
		ENetPacket * packet = (ENetPacket *) enet_malloc (sizeof (ENetPacket));
		if (packet == NULL)
			return NULL;

		if (flags & ENET_PACKET_FLAG_NO_ALLOCATE)
			packet -> data = (enet_uint8 *) data;
		else
		{
			packet -> data = (enet_uint8 *) enet_malloc (dataLength);
			if (packet -> data == NULL)
			{
				enet_free (packet);
				return NULL;
			}

			if (data != NULL)
				memcpy (packet -> data, data, dataLength);
		}

		packet -> referenceCount = 0;
		packet -> flags = flags;
		packet -> dataLength = dataLength;
		packet -> freeCallback = NULL;

		return packet;
	}

	/** Destroys the packet and deallocates its data.
	@param packet packet to be destroyed
	*/
	void
		enet_packet_destroy (ENetPacket * packet)
	{
		if (packet -> freeCallback != NULL)
			(* packet -> freeCallback) (packet);
		if (! (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE))
			enet_free (packet -> data);
		enet_free (packet);
	}

	/** Attempts to resize the data in the packet to length specified in the 
	dataLength parameter 
	@param packet packet to resize
	@param dataLength new size for the packet data
	@returns 0 on success, < 0 on failure
	*/
	int
		enet_packet_resize (ENetPacket * packet, size_t dataLength)
	{
		enet_uint8 * newData;

		if (dataLength <= packet -> dataLength || (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE))
		{
			packet -> dataLength = dataLength;

			return 0;
		}

		newData = (enet_uint8 *) enet_malloc (dataLength);
		if (newData == NULL)
			return -1;

		memcpy (newData, packet -> data, packet -> dataLength);
		enet_free (packet -> data);

		packet -> data = newData;
		packet -> dataLength = dataLength;

		return 0;
	}

	static int initializedCRC32 = 0;
	static enet_uint32 crcTable [256];

	static enet_uint32 
		reflect_crc (int val, int bits)
	{
		int result = 0, bit;

		for (bit = 0; bit < bits; bit ++)
		{
			if(val & 1) result |= 1 << (bits - 1 - bit); 
			val >>= 1;
		}

		return result;
	}

	static void 
		initialize_crc32 ()
	{
		int byte;

		for (byte = 0; byte < 256; ++ byte)
		{
			enet_uint32 crc = reflect_crc (byte, 8) << 24;
			int offset;

			for(offset = 0; offset < 8; ++ offset)
			{
				if (crc & 0x80000000)
					crc = (crc << 1) ^ 0x04c11db7;
				else
					crc <<= 1;
			}

			crcTable [byte] = reflect_crc (crc, 32);
		}

		initializedCRC32 = 1;
	}

	enet_uint32
		enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
	{
		enet_uint32 crc = 0xFFFFFFFF;

		if (! initializedCRC32) initialize_crc32 ();

		while (bufferCount -- > 0)
		{
			const enet_uint8 * data = (const enet_uint8 *) buffers -> data,
				* dataEnd = & data [buffers -> dataLength];

			while (data < dataEnd)
			{
				crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++];        
			}

			++ buffers;
		}

		return ENET_HOST_TO_NET_32 (~ crc);
	}

	void
		enet_peer_throttle_configure (ENetPeer * peer, enet_uint32 interval, enet_uint32 acceleration, enet_uint32 deceleration)
	{
		ENetProtocol command;

		peer -> packetThrottleInterval = interval;
		peer -> packetThrottleAcceleration = acceleration;
		peer -> packetThrottleDeceleration = deceleration;

		command.header.command = ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
		command.header.channelID = 0xFF;

		command.throttleConfigure.packetThrottleInterval = ENET_HOST_TO_NET_32 (interval);
		command.throttleConfigure.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (acceleration);
		command.throttleConfigure.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (deceleration);

		enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0);
	}

	int
		enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt)
	{
		if (peer -> lastRoundTripTime <= peer -> lastRoundTripTimeVariance)
		{
			peer -> packetThrottle = peer -> packetThrottleLimit;
		}
		else
			if (rtt < peer -> lastRoundTripTime)
			{
				peer -> packetThrottle += peer -> packetThrottleAcceleration;

				if (peer -> packetThrottle > peer -> packetThrottleLimit)
					peer -> packetThrottle = peer -> packetThrottleLimit;

				return 1;
			}
			else
				if (rtt > peer -> lastRoundTripTime + 2 * peer -> lastRoundTripTimeVariance)
				{
					if (peer -> packetThrottle > peer -> packetThrottleDeceleration)
						peer -> packetThrottle -= peer -> packetThrottleDeceleration;
					else
						peer -> packetThrottle = 0;

					return -1;
				}

				return 0;
	}

	/** Queues a packet to be sent.
	@param peer destination for the packet
	@param channelID channel on which to send
	@param packet packet to send
	@retval 0 on success
	@retval < 0 on failure
	*/
	int
		enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet)
	{
		ENetChannel * channel = & peer -> channels [channelID];
		ENetProtocol command;
		size_t fragmentLength;

		if (peer -> state != ENET_PEER_STATE_CONNECTED ||
			channelID >= peer -> channelCount)
			return -1;

		fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment);
		if (peer -> host -> checksum != NULL)
			fragmentLength -= sizeof(enet_uint32);

		if (packet -> dataLength > fragmentLength)
		{
			enet_uint32 fragmentCount = ENET_HOST_TO_NET_32 ((packet -> dataLength + fragmentLength - 1) / fragmentLength),
				fragmentNumber,
				fragmentOffset;
			enet_uint8 commandNumber;
			enet_uint16 startSequenceNumber; 
			ENetList fragments;
			ENetOutgoingCommand * fragment;

			if ((packet -> flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT)) == ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT &&
				channel -> outgoingUnreliableSequenceNumber < 0xFFFF)
			{
				commandNumber = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT;
				startSequenceNumber = ENET_HOST_TO_NET_16 (channel -> outgoingUnreliableSequenceNumber + 1);
			}
			else
			{
				commandNumber = ENET_PROTOCOL_COMMAND_SEND_FRAGMENT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
				startSequenceNumber = ENET_HOST_TO_NET_16 (channel -> outgoingReliableSequenceNumber + 1);
			}

			enet_list_clear (& fragments);

			for (fragmentNumber = 0,
				fragmentOffset = 0;
				fragmentOffset < packet -> dataLength;
			++ fragmentNumber,
				fragmentOffset += fragmentLength)
			{
				if (packet -> dataLength - fragmentOffset < fragmentLength)
					fragmentLength = packet -> dataLength - fragmentOffset;

				fragment = (ENetOutgoingCommand *) enet_malloc (sizeof (ENetOutgoingCommand));
				if (fragment == NULL)
				{
					while (! enet_list_empty (& fragments))
					{
						fragment = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (& fragments));

						enet_free (fragment);
					}

					return -1;
				}

				fragment -> fragmentOffset = fragmentOffset;
				fragment -> fragmentLength = fragmentLength;
				fragment -> packet = packet;
				fragment -> command.header.command = commandNumber;
				fragment -> command.header.channelID = channelID;
				fragment -> command.sendFragment.startSequenceNumber = startSequenceNumber;
				fragment -> command.sendFragment.dataLength = ENET_HOST_TO_NET_16 (fragmentLength);
				fragment -> command.sendFragment.fragmentCount = fragmentCount;
				fragment -> command.sendFragment.fragmentNumber = ENET_HOST_TO_NET_32 (fragmentNumber);
				fragment -> command.sendFragment.totalLength = ENET_HOST_TO_NET_32 (packet -> dataLength);
				fragment -> command.sendFragment.fragmentOffset = ENET_NET_TO_HOST_32 (fragmentOffset);

				enet_list_insert (enet_list_end (& fragments), fragment);
			}

			packet -> referenceCount += fragmentNumber;

			while (! enet_list_empty (& fragments))
			{
				fragment = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (& fragments));

				enet_peer_setup_outgoing_command (peer, fragment);
			}

			return 0;
		}

		command.header.channelID = channelID;

		if ((packet -> flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNSEQUENCED)) == ENET_PACKET_FLAG_UNSEQUENCED)
		{
			command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED;
			command.sendUnsequenced.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength);
		}
		else 
			if (packet -> flags & ENET_PACKET_FLAG_RELIABLE || channel -> outgoingUnreliableSequenceNumber >= 0xFFFF)
			{
				command.header.command = ENET_PROTOCOL_COMMAND_SEND_RELIABLE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
				command.sendReliable.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength);
			}
			else
			{
				command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE;
				command.sendUnreliable.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength);
			}

			if (enet_peer_queue_outgoing_command (peer, & command, packet, 0, packet -> dataLength) == NULL)
				return -1;

			return 0;
	}

	/** Attempts to dequeue any incoming queued packet.
	@param peer peer to dequeue packets from
	@param channelID holds the channel ID of the channel the packet was received on success
	@returns a pointer to the packet, or NULL if there are no available incoming queued packets
	*/
	ENetPacket *
		enet_peer_receive (ENetPeer * peer, enet_uint8 * channelID)
	{
		ENetIncomingCommand * incomingCommand;
		ENetPacket * packet;

		if (enet_list_empty (& peer -> dispatchedCommands))
			return NULL;

		incomingCommand = (ENetIncomingCommand *) enet_list_remove (enet_list_begin (& peer -> dispatchedCommands));

		if (channelID != NULL)
			* channelID = incomingCommand -> command.header.channelID;

		packet = incomingCommand -> packet;

		-- packet -> referenceCount;

		if (incomingCommand -> fragments != NULL)
			enet_free (incomingCommand -> fragments);

		enet_free (incomingCommand);

		return packet;
	}

	static void
		enet_peer_reset_outgoing_commands (ENetList * queue)
	{
		ENetOutgoingCommand * outgoingCommand;

		while (! enet_list_empty (queue))
		{
			outgoingCommand = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (queue));

			if (outgoingCommand -> packet != NULL)
			{
				-- outgoingCommand -> packet -> referenceCount;

				if (outgoingCommand -> packet -> referenceCount == 0)
					enet_packet_destroy (outgoingCommand -> packet);
			}

			enet_free (outgoingCommand);
		}
	}

	static void
		enet_peer_remove_incoming_commands (ENetList * queue, ENetListIterator startCommand, ENetListIterator endCommand)
	{
		ENetListIterator currentCommand;    

		for (currentCommand = startCommand; currentCommand != endCommand; )
		{
			ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand;

			currentCommand = enet_list_next (currentCommand);

			enet_list_remove (& incomingCommand -> incomingCommandList);

			if (incomingCommand -> packet != NULL)
			{
				-- incomingCommand -> packet -> referenceCount;

				if (incomingCommand -> packet -> referenceCount == 0)
					enet_packet_destroy (incomingCommand -> packet);
			}

			if (incomingCommand -> fragments != NULL)
				enet_free (incomingCommand -> fragments);

			enet_free (incomingCommand);
		}
	}

	static void
		enet_peer_reset_incoming_commands (ENetList * queue)
	{
		enet_peer_remove_incoming_commands(queue, enet_list_begin (queue), enet_list_end(queue));
	}

	void
		enet_peer_reset_queues (ENetPeer * peer)
	{
		ENetChannel * channel;

		if (peer -> needsDispatch)
		{
			enet_list_remove (& peer -> dispatchList);

			peer -> needsDispatch = 0;
		}

		while (! enet_list_empty (& peer -> acknowledgements))
			enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements)));

		enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands);
		enet_peer_reset_outgoing_commands (& peer -> sentUnreliableCommands);
		enet_peer_reset_outgoing_commands (& peer -> outgoingReliableCommands);
		enet_peer_reset_outgoing_commands (& peer -> outgoingUnreliableCommands);
		enet_peer_reset_incoming_commands (& peer -> dispatchedCommands);

		if (peer -> channels != NULL && peer -> channelCount > 0)
		{
			for (channel = peer -> channels;
				channel < & peer -> channels [peer -> channelCount];
				++ channel)
			{
				enet_peer_reset_incoming_commands (& channel -> incomingReliableCommands);
				enet_peer_reset_incoming_commands (& channel -> incomingUnreliableCommands);
			}

			enet_free (peer -> channels);
		}

		peer -> channels = NULL;
		peer -> channelCount = 0;
	}

	/** Forcefully disconnects a peer.
	@param peer peer to forcefully disconnect
	@remarks The foreign host represented by the peer is not notified of the disconnection and will timeout
	on its connection to the local host.
	*/
	void
		enet_peer_reset (ENetPeer * peer)
	{
		peer -> outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID;
		peer -> connectID = 0;

		peer -> state = ENET_PEER_STATE_DISCONNECTED;

		peer -> incomingBandwidth = 0;
		peer -> outgoingBandwidth = 0;
		peer -> incomingBandwidthThrottleEpoch = 0;
		peer -> outgoingBandwidthThrottleEpoch = 0;
		peer -> incomingDataTotal = 0;
		peer -> outgoingDataTotal = 0;
		peer -> lastSendTime = 0;
		peer -> lastReceiveTime = 0;
		peer -> nextTimeout = 0;
		peer -> earliestTimeout = 0;
		peer -> packetLossEpoch = 0;
		peer -> packetsSent = 0;
		peer -> packetsLost = 0;
		peer -> packetLoss = 0;
		peer -> packetLossVariance = 0;
		peer -> packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE;
		peer -> packetThrottleLimit = ENET_PEER_PACKET_THROTTLE_SCALE;
		peer -> packetThrottleCounter = 0;
		peer -> packetThrottleEpoch = 0;
		peer -> packetThrottleAcceleration = ENET_PEER_PACKET_THROTTLE_ACCELERATION;
		peer -> packetThrottleDeceleration = ENET_PEER_PACKET_THROTTLE_DECELERATION;
		peer -> packetThrottleInterval = ENET_PEER_PACKET_THROTTLE_INTERVAL;
		peer -> lastRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME;
		peer -> lowestRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME;
		peer -> lastRoundTripTimeVariance = 0;
		peer -> highestRoundTripTimeVariance = 0;
		peer -> roundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME;
		peer -> roundTripTimeVariance = 0;
		peer -> mtu = peer -> host -> mtu;
		peer -> reliableDataInTransit = 0;
		peer -> outgoingReliableSequenceNumber = 0;
		peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
		peer -> incomingUnsequencedGroup = 0;
		peer -> outgoingUnsequencedGroup = 0;
		peer -> eventData = 0;

		memset (peer -> unsequencedWindow, 0, sizeof (peer -> unsequencedWindow));

		enet_peer_reset_queues (peer);
	}

	/** Sends a ping request to a peer.
	@param peer destination for the ping request
	@remarks ping requests factor into the mean round trip time as designated by the 
	roundTripTime field in the ENetPeer structure.  Enet automatically pings all connected
	peers at regular intervals, however, this function may be called to ensure more
	frequent ping requests.
	*/
	void
		enet_peer_ping (ENetPeer * peer)
	{
		ENetProtocol command;

		if (peer -> state != ENET_PEER_STATE_CONNECTED)
			return;

		command.header.command = ENET_PROTOCOL_COMMAND_PING | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
		command.header.channelID = 0xFF;

		enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0);
	}

	/** Force an immediate disconnection from a peer.
	@param peer peer to disconnect
	@param data data describing the disconnection
	@remarks No ENET_EVENT_DISCONNECT event will be generated. The foreign peer is not
	guarenteed to receive the disconnect notification, and is reset immediately upon
	return from this function.
	*/
	void
		enet_peer_disconnect_now (ENetPeer * peer, enet_uint32 data)
	{
		ENetProtocol command;

		if (peer -> state == ENET_PEER_STATE_DISCONNECTED)
			return;

		if (peer -> state != ENET_PEER_STATE_ZOMBIE &&
			peer -> state != ENET_PEER_STATE_DISCONNECTING)
		{
			enet_peer_reset_queues (peer);

			command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED;
			command.header.channelID = 0xFF;
			command.disconnect.data = ENET_HOST_TO_NET_32 (data);

			enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0);

			enet_host_flush (peer -> host);
		}

		enet_peer_reset (peer);
	}

	/** Request a disconnection from a peer.
	@param peer peer to request a disconnection
	@param data data describing the disconnection
	@remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service()
	once the disconnection is complete.
	*/
	void
		enet_peer_disconnect (ENetPeer * peer, enet_uint32 data)
	{
		ENetProtocol command;

		if (peer -> state == ENET_PEER_STATE_DISCONNECTING ||
			peer -> state == ENET_PEER_STATE_DISCONNECTED ||
			peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT ||
			peer -> state == ENET_PEER_STATE_ZOMBIE)
			return;

		enet_peer_reset_queues (peer);

		command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT;
		command.header.channelID = 0xFF;
		command.disconnect.data = ENET_HOST_TO_NET_32 (data);

		if (peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER)
			command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
		else
			command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED;      

		enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0);

		if (peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER)
			peer -> state = ENET_PEER_STATE_DISCONNECTING;
		else
		{
			enet_host_flush (peer -> host);
			enet_peer_reset (peer);
		}
	}

	/** Request a disconnection from a peer, but only after all queued outgoing packets are sent.
	@param peer peer to request a disconnection
	@param data data describing the disconnection
	@remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service()
	once the disconnection is complete.
	*/
	void
		enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data)
	{   
		if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && 
			! (enet_list_empty (& peer -> outgoingReliableCommands) &&
			enet_list_empty (& peer -> outgoingUnreliableCommands) && 
			enet_list_empty (& peer -> sentReliableCommands)))
		{
			peer -> state = ENET_PEER_STATE_DISCONNECT_LATER;
			peer -> eventData = data;
		}
		else
			enet_peer_disconnect (peer, data);
	}

	ENetAcknowledgement *
		enet_peer_queue_acknowledgement (ENetPeer * peer, const ENetProtocol * command, enet_uint16 sentTime)
	{
		ENetAcknowledgement * acknowledgement;

		if (command -> header.channelID < peer -> channelCount)
		{
			ENetChannel * channel = & peer -> channels [command -> header.channelID];
			enet_uint16 reliableWindow = command -> header.reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE,
				currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;

			if (command -> header.reliableSequenceNumber < channel -> incomingReliableSequenceNumber)
				reliableWindow += ENET_PEER_RELIABLE_WINDOWS;

			if (reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1 && reliableWindow <= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS)
				return NULL;
		}

		acknowledgement = (ENetAcknowledgement *) enet_malloc (sizeof (ENetAcknowledgement));
		if (acknowledgement == NULL)
			return NULL;

		peer -> outgoingDataTotal += sizeof (ENetProtocolAcknowledge);

		acknowledgement -> sentTime = sentTime;
		acknowledgement -> command = * command;

		enet_list_insert (enet_list_end (& peer -> acknowledgements), acknowledgement);

		return acknowledgement;
	}

	void
		enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand)
	{
		ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID];

		peer -> outgoingDataTotal += enet_protocol_command_size (outgoingCommand -> command.header.command) + outgoingCommand -> fragmentLength;

		if (outgoingCommand -> command.header.channelID == 0xFF)
		{
			++ peer -> outgoingReliableSequenceNumber;

			outgoingCommand -> reliableSequenceNumber = peer -> outgoingReliableSequenceNumber;
			outgoingCommand -> unreliableSequenceNumber = 0;
		}
		else
			if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
			{
				++ channel -> outgoingReliableSequenceNumber;
				channel -> outgoingUnreliableSequenceNumber = 0;

				outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
				outgoingCommand -> unreliableSequenceNumber = 0;
			}
			else
				if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED)
				{
					++ peer -> outgoingUnsequencedGroup;

					outgoingCommand -> reliableSequenceNumber = 0;
					outgoingCommand -> unreliableSequenceNumber = 0;
				}
				else
				{
					if (outgoingCommand -> fragmentOffset == 0)
						++ channel -> outgoingUnreliableSequenceNumber;

					outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
					outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber;
				}

				outgoingCommand -> sendAttempts = 0;
				outgoingCommand -> sentTime = 0;
				outgoingCommand -> roundTripTimeout = 0;
				outgoingCommand -> roundTripTimeoutLimit = 0;
				outgoingCommand -> command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> reliableSequenceNumber);

				switch (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK)
				{
				case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE:
					outgoingCommand -> command.sendUnreliable.unreliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> unreliableSequenceNumber);
					break;

				case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED:
					outgoingCommand -> command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16 (peer -> outgoingUnsequencedGroup);
					break;

				default:
					break;
				}

				if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
					enet_list_insert (enet_list_end (& peer -> outgoingReliableCommands), outgoingCommand);
				else
					enet_list_insert (enet_list_end (& peer -> outgoingUnreliableCommands), outgoingCommand);
	}

	ENetOutgoingCommand *
		enet_peer_queue_outgoing_command (ENetPeer * peer, const ENetProtocol * command, ENetPacket * packet, enet_uint32 offset, enet_uint16 length)
	{
		ENetOutgoingCommand * outgoingCommand = (ENetOutgoingCommand *) enet_malloc (sizeof (ENetOutgoingCommand));
		if (outgoingCommand == NULL)
			return NULL;

		outgoingCommand -> command = * command;
		outgoingCommand -> fragmentOffset = offset;
		outgoingCommand -> fragmentLength = length;
		outgoingCommand -> packet = packet;
		if (packet != NULL)
			++ packet -> referenceCount;

		enet_peer_setup_outgoing_command (peer, outgoingCommand);

		return outgoingCommand;
	}

	void
		enet_peer_dispatch_incoming_unreliable_commands (ENetPeer * peer, ENetChannel * channel)
	{
		ENetListIterator droppedCommand, startCommand, currentCommand;

		for (droppedCommand = startCommand = currentCommand = enet_list_begin (& channel -> incomingUnreliableCommands);
			currentCommand != enet_list_end (& channel -> incomingUnreliableCommands);
			currentCommand = enet_list_next (currentCommand))
		{
			ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand;

			if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED)
				continue;
			else
				if (incomingCommand -> reliableSequenceNumber != channel -> incomingReliableSequenceNumber)
					break;
				else
					if (incomingCommand -> fragmentsRemaining <= 0)
						channel -> incomingUnreliableSequenceNumber = incomingCommand -> unreliableSequenceNumber;
					else
						if (startCommand == currentCommand)
							startCommand = enet_list_next (currentCommand);
						else
						{
							enet_list_move (enet_list_end (& peer -> dispatchedCommands), startCommand, enet_list_previous (currentCommand));

							if (! peer -> needsDispatch)
							{
								enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList);

								peer -> needsDispatch = 1;
							}

							droppedCommand = startCommand = enet_list_next (currentCommand); 
						}
		}

		if (startCommand != currentCommand)
		{
			enet_list_move (enet_list_end (& peer -> dispatchedCommands), startCommand, enet_list_previous (currentCommand));

			if (! peer -> needsDispatch)
			{
				enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList);

				peer -> needsDispatch = 1;
			}

			droppedCommand = startCommand = enet_list_next (currentCommand);
		}

		enet_peer_remove_incoming_commands (& channel -> incomingUnreliableCommands, enet_list_begin (& channel -> incomingUnreliableCommands), droppedCommand);
	}

	void
		enet_peer_dispatch_incoming_reliable_commands (ENetPeer * peer, ENetChannel * channel)
	{
		ENetListIterator currentCommand;

		for (currentCommand = enet_list_begin (& channel -> incomingReliableCommands);
			currentCommand != enet_list_end (& channel -> incomingReliableCommands);
			currentCommand = enet_list_next (currentCommand))
		{
			ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand;

			if (incomingCommand -> fragmentsRemaining > 0 ||
				incomingCommand -> reliableSequenceNumber != (enet_uint16) (channel -> incomingReliableSequenceNumber + 1))
				break;

			channel -> incomingReliableSequenceNumber = incomingCommand -> reliableSequenceNumber;

			if (incomingCommand -> fragmentCount > 0)
				channel -> incomingReliableSequenceNumber += incomingCommand -> fragmentCount - 1;
		} 

		if (currentCommand == enet_list_begin (& channel -> incomingReliableCommands))
			return;

		channel -> incomingUnreliableSequenceNumber = 0;

		enet_list_move (enet_list_end (& peer -> dispatchedCommands), enet_list_begin (& channel -> incomingReliableCommands), enet_list_previous (currentCommand));

		if (! peer -> needsDispatch)
		{
			enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList);

			peer -> needsDispatch = 1;
		}

		enet_peer_dispatch_incoming_unreliable_commands (peer, channel);
	}

	ENetIncomingCommand *
		enet_peer_queue_incoming_command (ENetPeer * peer, const ENetProtocol * command, ENetPacket * packet, enet_uint32 fragmentCount)
	{
		static ENetIncomingCommand dummyCommand;

		ENetChannel * channel = & peer -> channels [command -> header.channelID];
		enet_uint32 unreliableSequenceNumber = 0, reliableSequenceNumber;
		enet_uint16 reliableWindow, currentWindow;
		ENetIncomingCommand * incomingCommand;
		ENetListIterator currentCommand;

		if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER)
			goto freePacket;

		if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED)
		{
			reliableSequenceNumber = command -> header.reliableSequenceNumber;
			reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
			currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;

			if (reliableSequenceNumber < channel -> incomingReliableSequenceNumber)
				reliableWindow += ENET_PEER_RELIABLE_WINDOWS;

			if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1)
				goto freePacket;
		}

		switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK)
		{
		case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT:
		case ENET_PROTOCOL_COMMAND_SEND_RELIABLE:
			if (reliableSequenceNumber == channel -> incomingReliableSequenceNumber)
				goto freePacket;

			for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingReliableCommands));
				currentCommand != enet_list_end (& channel -> incomingReliableCommands);
				currentCommand = enet_list_previous (currentCommand))
			{
				incomingCommand = (ENetIncomingCommand *) currentCommand;

				if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber)
				{
					if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber)
						continue;
				}
				else
					if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber)
						break;

				if (incomingCommand -> reliableSequenceNumber <= reliableSequenceNumber)
				{
					if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber)
						break;

					goto freePacket;
				}
			}
			break;

		case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE:
		case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT:
			unreliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendUnreliable.unreliableSequenceNumber);

			if (reliableSequenceNumber == channel -> incomingReliableSequenceNumber && 
				unreliableSequenceNumber <= channel -> incomingUnreliableSequenceNumber)
				goto freePacket;

			for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingUnreliableCommands));
				currentCommand != enet_list_end (& channel -> incomingUnreliableCommands);
				currentCommand = enet_list_previous (currentCommand))
			{
				incomingCommand = (ENetIncomingCommand *) currentCommand;

				if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED)
					continue;

				if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber)
				{
					if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber)
						continue;
				}
				else
					if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber)
						break;

				if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber)
					break;

				if (incomingCommand -> reliableSequenceNumber > reliableSequenceNumber)
					continue;

				if (incomingCommand -> unreliableSequenceNumber <= unreliableSequenceNumber)
				{
					if (incomingCommand -> unreliableSequenceNumber < unreliableSequenceNumber)
						break;

					goto freePacket;
				}
			}
			break;

		case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED:
			currentCommand = enet_list_end (& channel -> incomingUnreliableCommands);
			break;

		default:
			goto freePacket;
		}

		incomingCommand = (ENetIncomingCommand *) enet_malloc (sizeof (ENetIncomingCommand));
		if (incomingCommand == NULL)
			goto notifyError;

		incomingCommand -> reliableSequenceNumber = command -> header.reliableSequenceNumber;
		incomingCommand -> unreliableSequenceNumber = unreliableSequenceNumber & 0xFFFF;
		incomingCommand -> command = * command;
		incomingCommand -> fragmentCount = fragmentCount;
		incomingCommand -> fragmentsRemaining = fragmentCount;
		incomingCommand -> packet = packet;
		incomingCommand -> fragments = NULL;

		if (fragmentCount > 0)
		{ 
			incomingCommand -> fragments = (enet_uint32 *) enet_malloc ((fragmentCount + 31) / 32 * sizeof (enet_uint32));
			if (incomingCommand -> fragments == NULL)
			{
				enet_free (incomingCommand);

				goto notifyError;
			}
			memset (incomingCommand -> fragments, 0, (fragmentCount + 31) / 32 * sizeof (enet_uint32));
		}

		if (packet != NULL)
			++ packet -> referenceCount;

		enet_list_insert (enet_list_next (currentCommand), incomingCommand);

		switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK)
		{
		case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT:
		case ENET_PROTOCOL_COMMAND_SEND_RELIABLE:
			enet_peer_dispatch_incoming_reliable_commands (peer, channel);
			break;

		default:
			enet_peer_dispatch_incoming_unreliable_commands (peer, channel);
			break;
		}

		return incomingCommand;

freePacket:
		if (fragmentCount > 0)
			goto notifyError;

		if (packet != NULL && packet -> referenceCount == 0)
			enet_packet_destroy (packet);

		return & dummyCommand;

notifyError:
		if (packet != NULL && packet -> referenceCount == 0)
			enet_packet_destroy (packet);

		return NULL;
	}

	static size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] =
	{
		0,
		sizeof (ENetProtocolAcknowledge),
		sizeof (ENetProtocolConnect),
		sizeof (ENetProtocolVerifyConnect),
		sizeof (ENetProtocolDisconnect),
		sizeof (ENetProtocolPing),
		sizeof (ENetProtocolSendReliable),
		sizeof (ENetProtocolSendUnreliable),
		sizeof (ENetProtocolSendFragment),
		sizeof (ENetProtocolSendUnsequenced),
		sizeof (ENetProtocolBandwidthLimit),
		sizeof (ENetProtocolThrottleConfigure),
		sizeof (ENetProtocolSendFragment)
	};

	size_t
		enet_protocol_command_size (enet_uint8 commandNumber)
	{
		return commandSizes [commandNumber & ENET_PROTOCOL_COMMAND_MASK];
	}

	static int
		enet_protocol_dispatch_incoming_commands (ENetHost * host, ENetEvent * event)
	{
		while (! enet_list_empty (& host -> dispatchQueue))
		{
			ENetPeer * peer = (ENetPeer *) enet_list_remove (enet_list_begin (& host -> dispatchQueue));

			peer -> needsDispatch = 0;

			switch (peer -> state)
			{
			case ENET_PEER_STATE_CONNECTION_PENDING:
			case ENET_PEER_STATE_CONNECTION_SUCCEEDED:
				peer -> state = ENET_PEER_STATE_CONNECTED;

				event -> type = ENET_EVENT_TYPE_CONNECT;
				event -> peer = peer;
				event -> data = peer -> eventData;

				return 1;

			case ENET_PEER_STATE_ZOMBIE:
				host -> recalculateBandwidthLimits = 1;

				event -> type = ENET_EVENT_TYPE_DISCONNECT;
				event -> peer = peer;
				event -> data = peer -> eventData;

				enet_peer_reset (peer);

				return 1;

			case ENET_PEER_STATE_CONNECTED:
				if (enet_list_empty (& peer -> dispatchedCommands))
					continue;

				event -> packet = enet_peer_receive (peer, & event -> channelID);
				if (event -> packet == NULL)
					continue;

				event -> type = ENET_EVENT_TYPE_RECEIVE;
				event -> peer = peer;

				if (! enet_list_empty (& peer -> dispatchedCommands))
				{
					peer -> needsDispatch = 1;

					enet_list_insert (enet_list_end (& host -> dispatchQueue), & peer -> dispatchList);
				}

				return 1;
			}
		}

		return 0;
	}

	static void
		enet_protocol_dispatch_state (ENetHost * host, ENetPeer * peer, ENetPeerState state)
	{
		peer -> state = state;

		if (! peer -> needsDispatch)
		{
			enet_list_insert (enet_list_end (& host -> dispatchQueue), & peer -> dispatchList);

			peer -> needsDispatch = 1;
		}    
	}

	static void
		enet_protocol_notify_connect (ENetHost * host, ENetPeer * peer, ENetEvent * event)
	{
		host -> recalculateBandwidthLimits = 1;

		if (event != NULL)
		{
			peer -> state = ENET_PEER_STATE_CONNECTED;

			event -> type = ENET_EVENT_TYPE_CONNECT;
			event -> peer = peer;
			event -> data = peer -> eventData;
		}
		else 
			enet_protocol_dispatch_state (host, peer, peer -> state == ENET_PEER_STATE_CONNECTING ? ENET_PEER_STATE_CONNECTION_SUCCEEDED : ENET_PEER_STATE_CONNECTION_PENDING);
	}

	static void
		enet_protocol_notify_disconnect (ENetHost * host, ENetPeer * peer, ENetEvent * event)
	{
		if (peer -> state >= ENET_PEER_STATE_CONNECTION_PENDING)
			host -> recalculateBandwidthLimits = 1;

		if (peer -> state != ENET_PEER_STATE_CONNECTING && peer -> state < ENET_PEER_STATE_CONNECTION_SUCCEEDED)
			enet_peer_reset (peer);
		else
			if (event != NULL)
			{
				event -> type = ENET_EVENT_TYPE_DISCONNECT;
				event -> peer = peer;
				event -> data = 0;

				enet_peer_reset (peer);
			}
			else 
			{
				peer -> eventData = 0;

				enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE);
			}
	}

	static void
		enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer)
	{
		ENetOutgoingCommand * outgoingCommand;

		while (! enet_list_empty (& peer -> sentUnreliableCommands))
		{
			outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentUnreliableCommands);

			enet_list_remove (& outgoingCommand -> outgoingCommandList);

			if (outgoingCommand -> packet != NULL)
			{
				-- outgoingCommand -> packet -> referenceCount;

				if (outgoingCommand -> packet -> referenceCount == 0)
					enet_packet_destroy (outgoingCommand -> packet);
			}

			enet_free (outgoingCommand);
		}
	}

	static ENetProtocolCommand
		enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID)
	{
		ENetOutgoingCommand * outgoingCommand;
		ENetListIterator currentCommand;
		ENetProtocolCommand commandNumber;
		int wasSent = 1;

		for (currentCommand = enet_list_begin (& peer -> sentReliableCommands);
			currentCommand != enet_list_end (& peer -> sentReliableCommands);
			currentCommand = enet_list_next (currentCommand))
		{
			outgoingCommand = (ENetOutgoingCommand *) currentCommand;

			if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber &&
				outgoingCommand -> command.header.channelID == channelID)
				break;
		}

		if (currentCommand == enet_list_end (& peer -> sentReliableCommands))
		{
			for (currentCommand = enet_list_begin (& peer -> outgoingReliableCommands);
				currentCommand != enet_list_end (& peer -> outgoingReliableCommands);
				currentCommand = enet_list_next (currentCommand))
			{
				outgoingCommand = (ENetOutgoingCommand *) currentCommand;

				if (outgoingCommand -> sendAttempts < 1) return ENET_PROTOCOL_COMMAND_NONE;

				if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber &&
					outgoingCommand -> command.header.channelID == channelID)
					break;
			}

			if (currentCommand == enet_list_end (& peer -> outgoingReliableCommands))
				return ENET_PROTOCOL_COMMAND_NONE;

			wasSent = 0;
		}

		if (channelID < peer -> channelCount)
		{
			ENetChannel * channel = & peer -> channels [channelID];
			enet_uint16 reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
			if (channel -> reliableWindows [reliableWindow] > 0)
			{
				-- channel -> reliableWindows [reliableWindow];
				if (! channel -> reliableWindows [reliableWindow])
					channel -> usedReliableWindows &= ~ (1 << reliableWindow);
			}
		}

		commandNumber = (ENetProtocolCommand) (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK);

		enet_list_remove (& outgoingCommand -> outgoingCommandList);

		if (outgoingCommand -> packet != NULL)
		{
			if (wasSent)
				peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength;

			-- outgoingCommand -> packet -> referenceCount;

			if (outgoingCommand -> packet -> referenceCount == 0)
				enet_packet_destroy (outgoingCommand -> packet);
		}

		enet_free (outgoingCommand);

		if (enet_list_empty (& peer -> sentReliableCommands))
			return commandNumber;

		outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentReliableCommands);

		peer -> nextTimeout = outgoingCommand -> sentTime + outgoingCommand -> roundTripTimeout;

		return commandNumber;
	} 

	static ENetPeer *
		enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENetProtocol * command)
	{
		enet_uint8 incomingSessionID, outgoingSessionID;
		enet_uint32 mtu, windowSize;
		ENetChannel * channel;
		size_t channelCount;
		ENetPeer * currentPeer;
		ENetProtocol verifyCommand;

		channelCount = ENET_NET_TO_HOST_32 (command -> connect.channelCount);

		if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT ||
			channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
			return NULL;

		for (currentPeer = host -> peers;
			currentPeer < & host -> peers [host -> peerCount];
			++ currentPeer)
		{
			if (currentPeer -> state != ENET_PEER_STATE_DISCONNECTED &&
				currentPeer -> address.host == host -> receivedAddress.host &&
				currentPeer -> address.port == host -> receivedAddress.port &&
				currentPeer -> connectID == command -> connect.connectID)
				return NULL;
		}

		for (currentPeer = host -> peers;
			currentPeer < & host -> peers [host -> peerCount];
			++ currentPeer)
		{
			if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED)
				break;
		}

		if (currentPeer >= & host -> peers [host -> peerCount])
			return NULL;

		if (channelCount > host -> channelLimit)
			channelCount = host -> channelLimit;
		currentPeer -> channels = (ENetChannel *) enet_malloc (channelCount * sizeof (ENetChannel));
		if (currentPeer -> channels == NULL)
			return NULL;
		currentPeer -> channelCount = channelCount;
		currentPeer -> state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT;
		currentPeer -> connectID = command -> connect.connectID;
		currentPeer -> address = host -> receivedAddress;
		currentPeer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> connect.outgoingPeerID);
		currentPeer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.incomingBandwidth);
		currentPeer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.outgoingBandwidth);
		currentPeer -> packetThrottleInterval = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleInterval);
		currentPeer -> packetThrottleAcceleration = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleAcceleration);
		currentPeer -> packetThrottleDeceleration = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleDeceleration);
		currentPeer -> eventData = ENET_NET_TO_HOST_32 (command -> connect.data);

		incomingSessionID = command -> connect.incomingSessionID == 0xFF ? currentPeer -> outgoingSessionID : command -> connect.incomingSessionID;
		incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT);
		if (incomingSessionID == currentPeer -> outgoingSessionID)
			incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT);
		currentPeer -> outgoingSessionID = incomingSessionID;

		outgoingSessionID = command -> connect.outgoingSessionID == 0xFF ? currentPeer -> incomingSessionID : command -> connect.outgoingSessionID;
		outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT);
		if (outgoingSessionID == currentPeer -> incomingSessionID)
			outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT);
		currentPeer -> incomingSessionID = outgoingSessionID;

		for (channel = currentPeer -> channels;
			channel < & currentPeer -> channels [channelCount];
			++ channel)
		{
			channel -> outgoingReliableSequenceNumber = 0;
			channel -> outgoingUnreliableSequenceNumber = 0;
			channel -> incomingReliableSequenceNumber = 0;
			channel -> incomingUnreliableSequenceNumber = 0;

			enet_list_clear (& channel -> incomingReliableCommands);
			enet_list_clear (& channel -> incomingUnreliableCommands);

			channel -> usedReliableWindows = 0;
			memset (channel -> reliableWindows, 0, sizeof (channel -> reliableWindows));
		}

		mtu = ENET_NET_TO_HOST_32 (command -> connect.mtu);

		if (mtu < ENET_PROTOCOL_MINIMUM_MTU)
			mtu = ENET_PROTOCOL_MINIMUM_MTU;
		else
			if (mtu > ENET_PROTOCOL_MAXIMUM_MTU)
				mtu = ENET_PROTOCOL_MAXIMUM_MTU;

		currentPeer -> mtu = mtu;

		if (host -> outgoingBandwidth == 0 &&
			currentPeer -> incomingBandwidth == 0)
			currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
		else
			if (host -> outgoingBandwidth == 0 ||
				currentPeer -> incomingBandwidth == 0)
				currentPeer -> windowSize = (ENET_MAX (host -> outgoingBandwidth, currentPeer -> incomingBandwidth) /
				ENET_PEER_WINDOW_SIZE_SCALE) *
				ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
			else
				currentPeer -> windowSize = (ENET_MIN (host -> outgoingBandwidth, currentPeer -> incomingBandwidth) /
				ENET_PEER_WINDOW_SIZE_SCALE) * 
				ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

		if (currentPeer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
			currentPeer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
		else
			if (currentPeer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
				currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

		if (host -> incomingBandwidth == 0)
			windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
		else
			windowSize = (host -> incomingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) *
			ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

		if (windowSize > ENET_NET_TO_HOST_32 (command -> connect.windowSize))
			windowSize = ENET_NET_TO_HOST_32 (command -> connect.windowSize);

		if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
			windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
		else
			if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
				windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

		verifyCommand.header.command = ENET_PROTOCOL_COMMAND_VERIFY_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
		verifyCommand.header.channelID = 0xFF;
		verifyCommand.verifyConnect.outgoingPeerID = ENET_HOST_TO_NET_16 (currentPeer -> incomingPeerID);
		verifyCommand.verifyConnect.incomingSessionID = incomingSessionID;
		verifyCommand.verifyConnect.outgoingSessionID = outgoingSessionID;
		verifyCommand.verifyConnect.mtu = ENET_HOST_TO_NET_16 (currentPeer -> mtu);
		verifyCommand.verifyConnect.windowSize = ENET_HOST_TO_NET_32 (windowSize);
		verifyCommand.verifyConnect.channelCount = ENET_HOST_TO_NET_32 (channelCount);
		verifyCommand.verifyConnect.incomingBandwidth = ENET_HOST_TO_NET_32 (host -> incomingBandwidth);
		verifyCommand.verifyConnect.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth);
		verifyCommand.verifyConnect.packetThrottleInterval = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleInterval);
		verifyCommand.verifyConnect.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleAcceleration);
		verifyCommand.verifyConnect.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleDeceleration);
		verifyCommand.verifyConnect.connectID = currentPeer -> connectID;

		enet_peer_queue_outgoing_command (currentPeer, & verifyCommand, NULL, 0, 0);

		return currentPeer;
	}

	static int
		enet_protocol_handle_send_reliable (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData)
	{
		ENetPacket * packet;
		size_t dataLength;

		if (command -> header.channelID >= peer -> channelCount ||
			(peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER))
			return -1;

		dataLength = ENET_NET_TO_HOST_16 (command -> sendReliable.dataLength);
		* currentData += dataLength;
		if (* currentData > & host -> receivedData [host -> receivedDataLength])
			return -1;

		packet = enet_packet_create ((const enet_uint8 *) command + sizeof (ENetProtocolSendReliable),
			dataLength,
			ENET_PACKET_FLAG_RELIABLE);
		if (packet == NULL ||
			enet_peer_queue_incoming_command (peer, command, packet, 0) == NULL)
			return -1;

		return 0;
	}

	static int
		enet_protocol_handle_send_unsequenced (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData)
	{
		ENetPacket * packet;
		enet_uint32 unsequencedGroup, index;
		size_t dataLength;

		if (command -> header.channelID >= peer -> channelCount ||
			(peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER))
			return -1;

		dataLength = ENET_NET_TO_HOST_16 (command -> sendUnsequenced.dataLength);
		* currentData += dataLength;
		if (* currentData > & host -> receivedData [host -> receivedDataLength])
			return -1; 

		unsequencedGroup = ENET_NET_TO_HOST_16 (command -> sendUnsequenced.unsequencedGroup);
		index = unsequencedGroup % ENET_PEER_UNSEQUENCED_WINDOW_SIZE;

		if (unsequencedGroup < peer -> incomingUnsequencedGroup)
			unsequencedGroup += 0x10000;

		if (unsequencedGroup >= (enet_uint32) peer -> incomingUnsequencedGroup + ENET_PEER_FREE_UNSEQUENCED_WINDOWS * ENET_PEER_UNSEQUENCED_WINDOW_SIZE)
			return 0;

		unsequencedGroup &= 0xFFFF;

		if (unsequencedGroup - index != peer -> incomingUnsequencedGroup)
		{
			peer -> incomingUnsequencedGroup = unsequencedGroup - index;

			memset (peer -> unsequencedWindow, 0, sizeof (peer -> unsequencedWindow));
		}
		else
			if (peer -> unsequencedWindow [index / 32] & (1 << (index % 32)))
				return 0;

		packet = enet_packet_create ((const enet_uint8 *) command + sizeof (ENetProtocolSendUnsequenced),
			dataLength,
			ENET_PACKET_FLAG_UNSEQUENCED);
		if (packet == NULL ||
			enet_peer_queue_incoming_command (peer, command, packet, 0) == NULL)
			return -1;

		peer -> unsequencedWindow [index / 32] |= 1 << (index % 32);

		return 0;
	}

	static int
		enet_protocol_handle_send_unreliable (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData)
	{
		ENetPacket * packet;
		size_t dataLength;

		if (command -> header.channelID >= peer -> channelCount ||
			(peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER))
			return -1;

		dataLength = ENET_NET_TO_HOST_16 (command -> sendUnreliable.dataLength);
		* currentData += dataLength;
		if (* currentData > & host -> receivedData [host -> receivedDataLength])
			return -1;

		packet = enet_packet_create ((const enet_uint8 *) command + sizeof (ENetProtocolSendUnreliable),
			dataLength,
			0);
		if (packet == NULL ||
			enet_peer_queue_incoming_command (peer, command, packet, 0) == NULL)
			return -1;

		return 0;
	}

	static int
		enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData)
	{
		enet_uint32 fragmentNumber,
			fragmentCount,
			fragmentOffset,
			fragmentLength,
			startSequenceNumber,
			totalLength;
		ENetChannel * channel;
		enet_uint16 startWindow, currentWindow;
		ENetListIterator currentCommand;
		ENetIncomingCommand * startCommand = NULL;

		if (command -> header.channelID >= peer -> channelCount ||
			(peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER))
			return -1;

		fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength);
		* currentData += fragmentLength;
		if (* currentData > & host -> receivedData [host -> receivedDataLength])
			return -1;

		channel = & peer -> channels [command -> header.channelID];
		startSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendFragment.startSequenceNumber);
		startWindow = startSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
		currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;

		if (startSequenceNumber < channel -> incomingReliableSequenceNumber)
			startWindow += ENET_PEER_RELIABLE_WINDOWS;

		if (startWindow < currentWindow || startWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1)
			return 0;

		fragmentNumber = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentNumber);
		fragmentCount = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentCount);
		fragmentOffset = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentOffset);
		totalLength = ENET_NET_TO_HOST_32 (command -> sendFragment.totalLength);

		if (fragmentOffset >= totalLength ||
			fragmentOffset + fragmentLength > totalLength ||
			fragmentNumber >= fragmentCount)
			return -1;

		for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingReliableCommands));
			currentCommand != enet_list_end (& channel -> incomingReliableCommands);
			currentCommand = enet_list_previous (currentCommand))
		{
			ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand;

			if (startSequenceNumber >= channel -> incomingReliableSequenceNumber)
			{
				if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber)
					continue;
			}
			else
				if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber)
					break;

			if (incomingCommand -> reliableSequenceNumber <= startSequenceNumber)
			{
				if (incomingCommand -> reliableSequenceNumber < startSequenceNumber)
					break;

				if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_FRAGMENT ||
					totalLength != incomingCommand -> packet -> dataLength ||
					fragmentCount != incomingCommand -> fragmentCount)
					return -1;

				startCommand = incomingCommand;
				break;
			}
		}

		if (startCommand == NULL)
		{
			ENetProtocol hostCommand = * command;
			ENetPacket * packet = enet_packet_create (NULL, totalLength, ENET_PACKET_FLAG_RELIABLE);
			if (packet == NULL)
				return -1;

			hostCommand.header.reliableSequenceNumber = startSequenceNumber;

			startCommand = enet_peer_queue_incoming_command (peer, & hostCommand, packet, fragmentCount);
			if (startCommand == NULL)
				return -1;
		}

		if ((startCommand -> fragments [fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0)
		{
			-- startCommand -> fragmentsRemaining;

			startCommand -> fragments [fragmentNumber / 32] |= (1 << (fragmentNumber % 32));

			if (fragmentOffset + fragmentLength > startCommand -> packet -> dataLength)
				fragmentLength = startCommand -> packet -> dataLength - fragmentOffset;

			memcpy (startCommand -> packet -> data + fragmentOffset,
				(enet_uint8 *) command + sizeof (ENetProtocolSendFragment),
				fragmentLength);

			if (startCommand -> fragmentsRemaining <= 0)
				enet_peer_dispatch_incoming_reliable_commands (peer, channel);
		}

		return 0;
	}

	static int
		enet_protocol_handle_send_unreliable_fragment (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData)
	{
		enet_uint32 fragmentNumber,
			fragmentCount,
			fragmentOffset,
			fragmentLength,
			reliableSequenceNumber,
			startSequenceNumber,
			totalLength;
		enet_uint16 reliableWindow, currentWindow;
		ENetChannel * channel;
		ENetListIterator currentCommand;
		ENetIncomingCommand * startCommand = NULL;

		if (command -> header.channelID >= peer -> channelCount ||
			(peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER))
			return -1;

		fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength);
		* currentData += fragmentLength;
		if (* currentData > & host -> receivedData [host -> receivedDataLength])
			return -1;

		channel = & peer -> channels [command -> header.channelID];
		reliableSequenceNumber = command -> header.reliableSequenceNumber;
		startSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendFragment.startSequenceNumber);

		reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
		currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;

		if (reliableSequenceNumber < channel -> incomingReliableSequenceNumber)
			reliableWindow += ENET_PEER_RELIABLE_WINDOWS;

		if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1)
			return 0;

		if (reliableSequenceNumber == channel -> incomingReliableSequenceNumber &&
			startSequenceNumber <= channel -> incomingUnreliableSequenceNumber)
			return 0;

		fragmentNumber = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentNumber);
		fragmentCount = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentCount);
		fragmentOffset = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentOffset);
		totalLength = ENET_NET_TO_HOST_32 (command -> sendFragment.totalLength);

		if (fragmentOffset >= totalLength ||
			fragmentOffset + fragmentLength > totalLength ||
			fragmentNumber >= fragmentCount)
			return -1;

		for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingUnreliableCommands));
			currentCommand != enet_list_end (& channel -> incomingUnreliableCommands);
			currentCommand = enet_list_previous (currentCommand))
		{
			ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand;

			if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber)
			{
				if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber)
					continue;
			}
			else
				if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber)
					break;

			if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber)
				break;

			if (incomingCommand -> reliableSequenceNumber > reliableSequenceNumber)
				continue;

			if (incomingCommand -> unreliableSequenceNumber <= startSequenceNumber)
			{
				if (incomingCommand -> unreliableSequenceNumber < startSequenceNumber)
					break;

				if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT ||
					totalLength != incomingCommand -> packet -> dataLength ||
					fragmentCount != incomingCommand -> fragmentCount)
					return -1;

				startCommand = incomingCommand;
				break;
			}
		}

		if (startCommand == NULL)
		{
			ENetPacket * packet = enet_packet_create (NULL, totalLength, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
			if (packet == NULL)
				return -1;

			startCommand = enet_peer_queue_incoming_command (peer, command, packet, fragmentCount);
			if (startCommand == NULL)
				return -1;
		}

		if ((startCommand -> fragments [fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0)
		{
			-- startCommand -> fragmentsRemaining;

			startCommand -> fragments [fragmentNumber / 32] |= (1 << (fragmentNumber % 32));

			if (fragmentOffset + fragmentLength > startCommand -> packet -> dataLength)
				fragmentLength = startCommand -> packet -> dataLength - fragmentOffset;

			memcpy (startCommand -> packet -> data + fragmentOffset,
				(enet_uint8 *) command + sizeof (ENetProtocolSendFragment),
				fragmentLength);

			if (startCommand -> fragmentsRemaining <= 0)
				enet_peer_dispatch_incoming_unreliable_commands (peer, channel);
		}

		return 0;
	}

	static int
		enet_protocol_handle_ping (ENetHost * host, ENetPeer * peer, const ENetProtocol * command)
	{
		return 0;
	}

	static int
		enet_protocol_handle_bandwidth_limit (ENetHost * host, ENetPeer * peer, const ENetProtocol * command)
	{
		peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> bandwidthLimit.incomingBandwidth);
		peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> bandwidthLimit.outgoingBandwidth);

		if (peer -> incomingBandwidth == 0 && host -> outgoingBandwidth == 0)
			peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
		else
			peer -> windowSize = (ENET_MIN (peer -> incomingBandwidth, host -> outgoingBandwidth) /
			ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

		if (peer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
			peer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
		else
			if (peer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
				peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

		return 0;
	}

	static int
		enet_protocol_handle_throttle_configure (ENetHost * host, ENetPeer * peer, const ENetProtocol * command)
	{
		peer -> packetThrottleInterval = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleInterval);
		peer -> packetThrottleAcceleration = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleAcceleration);
		peer -> packetThrottleDeceleration = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleDeceleration);

		return 0;
	}

	static int
		enet_protocol_handle_disconnect (ENetHost * host, ENetPeer * peer, const ENetProtocol * command)
	{
		if (peer -> state == ENET_PEER_STATE_ZOMBIE || peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT)
			return 0;

		enet_peer_reset_queues (peer);

		if (peer -> state == ENET_PEER_STATE_CONNECTION_SUCCEEDED || peer -> state == ENET_PEER_STATE_DISCONNECTING)
			enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE);
		else
			if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)
			{
				if (peer -> state == ENET_PEER_STATE_CONNECTION_PENDING) host -> recalculateBandwidthLimits = 1;

				enet_peer_reset (peer);
			}
			else
				if (command -> header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
					peer -> state = ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT;
				else
					enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE);

		if (peer -> state != ENET_PEER_STATE_DISCONNECTED)
			peer -> eventData = ENET_NET_TO_HOST_32 (command -> disconnect.data);

		return 0;
	}

	static int
		enet_protocol_handle_acknowledge (ENetHost * host, ENetEvent * event, ENetPeer * peer, const ENetProtocol * command)
	{
		enet_uint32 roundTripTime,
			receivedSentTime,
			receivedReliableSequenceNumber;
		ENetProtocolCommand commandNumber;

		receivedSentTime = ENET_NET_TO_HOST_16 (command -> acknowledge.receivedSentTime);
		receivedSentTime |= host -> serviceTime & 0xFFFF0000;
		if ((receivedSentTime & 0x8000) > (host -> serviceTime & 0x8000))
			receivedSentTime -= 0x10000;

		if (ENET_TIME_LESS (host -> serviceTime, receivedSentTime))
			return 0;

		peer -> lastReceiveTime = host -> serviceTime;
		peer -> earliestTimeout = 0;

		roundTripTime = ENET_TIME_DIFFERENCE (host -> serviceTime, receivedSentTime);

		enet_peer_throttle (peer, roundTripTime);

		peer -> roundTripTimeVariance -= peer -> roundTripTimeVariance / 4;

		if (roundTripTime >= peer -> roundTripTime)
		{
			peer -> roundTripTime += (roundTripTime - peer -> roundTripTime) / 8;
			peer -> roundTripTimeVariance += (roundTripTime - peer -> roundTripTime) / 4;
		}
		else
		{
			peer -> roundTripTime -= (peer -> roundTripTime - roundTripTime) / 8;
			peer -> roundTripTimeVariance += (peer -> roundTripTime - roundTripTime) / 4;
		}

		if (peer -> roundTripTime < peer -> lowestRoundTripTime)
			peer -> lowestRoundTripTime = peer -> roundTripTime;

		if (peer -> roundTripTimeVariance > peer -> highestRoundTripTimeVariance) 
			peer -> highestRoundTripTimeVariance = peer -> roundTripTimeVariance;

		if (peer -> packetThrottleEpoch == 0 ||
			ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> packetThrottleEpoch) >= peer -> packetThrottleInterval)
		{
			peer -> lastRoundTripTime = peer -> lowestRoundTripTime;
			peer -> lastRoundTripTimeVariance = peer -> highestRoundTripTimeVariance;
			peer -> lowestRoundTripTime = peer -> roundTripTime;
			peer -> highestRoundTripTimeVariance = peer -> roundTripTimeVariance;
			peer -> packetThrottleEpoch = host -> serviceTime;
		}

		receivedReliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> acknowledge.receivedReliableSequenceNumber);

		commandNumber = enet_protocol_remove_sent_reliable_command (peer, receivedReliableSequenceNumber, command -> header.channelID);

		switch (peer -> state)
		{
		case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT:
			if (commandNumber != ENET_PROTOCOL_COMMAND_VERIFY_CONNECT)
				return -1;

			enet_protocol_notify_connect (host, peer, event);
			break;

		case ENET_PEER_STATE_DISCONNECTING:
			if (commandNumber != ENET_PROTOCOL_COMMAND_DISCONNECT)
				return -1;

			enet_protocol_notify_disconnect (host, peer, event);
			break;

		case ENET_PEER_STATE_DISCONNECT_LATER:
			if (enet_list_empty (& peer -> outgoingReliableCommands) &&
				enet_list_empty (& peer -> outgoingUnreliableCommands) &&   
				enet_list_empty (& peer -> sentReliableCommands))
				enet_peer_disconnect (peer, peer -> eventData);
			break;
		}

		return 0;
	}

	static int
		enet_protocol_handle_verify_connect (ENetHost * host, ENetEvent * event, ENetPeer * peer, const ENetProtocol * command)
	{
		enet_uint32 mtu, windowSize;
		size_t channelCount;

		if (peer -> state != ENET_PEER_STATE_CONNECTING)
			return 0;

		channelCount = ENET_NET_TO_HOST_32 (command -> verifyConnect.channelCount);

		if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT ||
			ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleInterval) != peer -> packetThrottleInterval ||
			ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleAcceleration) != peer -> packetThrottleAcceleration ||
			ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleDeceleration) != peer -> packetThrottleDeceleration ||
			command -> verifyConnect.connectID != peer -> connectID)
		{
			peer -> eventData = 0;

			enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE);

			return -1;
		}

		enet_protocol_remove_sent_reliable_command (peer, 1, 0xFF);

		if (channelCount < peer -> channelCount)
			peer -> channelCount = channelCount;

		peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> verifyConnect.outgoingPeerID);
		peer -> incomingSessionID = command -> verifyConnect.incomingSessionID;
		peer -> outgoingSessionID = command -> verifyConnect.outgoingSessionID;

		mtu = ENET_NET_TO_HOST_32 (command -> verifyConnect.mtu);

		if (mtu < ENET_PROTOCOL_MINIMUM_MTU)
			mtu = ENET_PROTOCOL_MINIMUM_MTU;
		else 
			if (mtu > ENET_PROTOCOL_MAXIMUM_MTU)
				mtu = ENET_PROTOCOL_MAXIMUM_MTU;

		if (mtu < peer -> mtu)
			peer -> mtu = mtu;

		windowSize = ENET_NET_TO_HOST_32 (command -> verifyConnect.windowSize);

		if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
			windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

		if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
			windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

		if (windowSize < peer -> windowSize)
			peer -> windowSize = windowSize;

		peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> verifyConnect.incomingBandwidth);
		peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> verifyConnect.outgoingBandwidth);

		enet_protocol_notify_connect (host, peer, event);
		return 0;
	}

	static int
		enet_protocol_handle_incoming_commands (ENetHost * host, ENetEvent * event)
	{
		ENetProtocolHeader * header;
		ENetProtocol * command;
		ENetPeer * peer;
		enet_uint8 * currentData;
		size_t headerSize;
		enet_uint16 peerID, flags;
		enet_uint8 sessionID;

		if (host -> receivedDataLength < (size_t) & ((ENetProtocolHeader *) 0) -> sentTime)
			return 0;

		header = (ENetProtocolHeader *) host -> receivedData;

		peerID = ENET_NET_TO_HOST_16 (header -> peerID);
		sessionID = (peerID & ENET_PROTOCOL_HEADER_SESSION_MASK) >> ENET_PROTOCOL_HEADER_SESSION_SHIFT;
		flags = peerID & ENET_PROTOCOL_HEADER_FLAG_MASK;
		peerID &= ~ (ENET_PROTOCOL_HEADER_FLAG_MASK | ENET_PROTOCOL_HEADER_SESSION_MASK);

		headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME ? sizeof (ENetProtocolHeader) : (size_t) & ((ENetProtocolHeader *) 0) -> sentTime);
		if (host -> checksum != NULL)
			headerSize += sizeof (enet_uint32);

		if (peerID == ENET_PROTOCOL_MAXIMUM_PEER_ID)
			peer = NULL;
		else
			if (peerID >= host -> peerCount)
				return 0;
			else
			{
				peer = & host -> peers [peerID];

				if (peer -> state == ENET_PEER_STATE_DISCONNECTED ||
					peer -> state == ENET_PEER_STATE_ZOMBIE ||
					((host -> receivedAddress.host != peer -> address.host ||
					host -> receivedAddress.port != peer -> address.port) &&
					peer -> address.host != ENET_HOST_BROADCAST) ||
					(peer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID &&
					sessionID != peer -> incomingSessionID))
					return 0;
			}

			if (flags & ENET_PROTOCOL_HEADER_FLAG_COMPRESSED)
			{
				size_t originalSize;
				if (host -> compressor.context == NULL || host -> compressor.decompress == NULL)
					return 0;

				originalSize = host -> compressor.decompress (host -> compressor.context,
					host -> receivedData + headerSize, 
					host -> receivedDataLength - headerSize, 
					host -> packetData [1] + headerSize, 
					sizeof (host -> packetData [1]) - headerSize);
				if (originalSize <= 0 || originalSize > sizeof (host -> packetData [1]) - headerSize)
					return 0;

				memcpy (host -> packetData [1], header, headerSize);
				host -> receivedData = host -> packetData [1];
				host -> receivedDataLength = headerSize + originalSize;
			}

			if (host -> checksum != NULL)
			{
				enet_uint32 * checksum = (enet_uint32 *) & host -> receivedData [headerSize - sizeof (enet_uint32)],
					desiredChecksum = * checksum;
				ENetBuffer buffer;

				* checksum = peer != NULL ? peer -> connectID : 0;

				buffer.data = host -> receivedData;
				buffer.dataLength = host -> receivedDataLength;

				if (host -> checksum (& buffer, 1) != desiredChecksum)
					return 0;
			}

			if (peer != NULL)
			{
				peer -> address.host = host -> receivedAddress.host;
				peer -> address.port = host -> receivedAddress.port;
				peer -> incomingDataTotal += host -> receivedDataLength;
			}

			currentData = host -> receivedData + headerSize;

			while (currentData < & host -> receivedData [host -> receivedDataLength])
			{
				enet_uint8 commandNumber;
				size_t commandSize;

				command = (ENetProtocol *) currentData;

				if (currentData + sizeof (ENetProtocolCommandHeader) > & host -> receivedData [host -> receivedDataLength])
					break;

				commandNumber = command -> header.command & ENET_PROTOCOL_COMMAND_MASK;
				if (commandNumber >= ENET_PROTOCOL_COMMAND_COUNT) 
					break;

				commandSize = commandSizes [commandNumber];
				if (commandSize == 0 || currentData + commandSize > & host -> receivedData [host -> receivedDataLength])
					break;

				currentData += commandSize;

				if (peer == NULL && commandNumber != ENET_PROTOCOL_COMMAND_CONNECT)
					break;

				command -> header.reliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> header.reliableSequenceNumber);

				switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK)
				{
				case ENET_PROTOCOL_COMMAND_ACKNOWLEDGE:
					if (enet_protocol_handle_acknowledge (host, event, peer, command))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_CONNECT:
					peer = enet_protocol_handle_connect (host, header, command);
					if (peer == NULL)
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_VERIFY_CONNECT:
					if (enet_protocol_handle_verify_connect (host, event, peer, command))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_DISCONNECT:
					if (enet_protocol_handle_disconnect (host, peer, command))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_PING:
					if (enet_protocol_handle_ping (host, peer, command))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_SEND_RELIABLE:
					if (enet_protocol_handle_send_reliable (host, peer, command, & currentData))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE:
					if (enet_protocol_handle_send_unreliable (host, peer, command, & currentData))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED:
					if (enet_protocol_handle_send_unsequenced (host, peer, command, & currentData))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT:
					if (enet_protocol_handle_send_fragment (host, peer, command, & currentData))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT:
					if (enet_protocol_handle_bandwidth_limit (host, peer, command))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE:
					if (enet_protocol_handle_throttle_configure (host, peer, command))
						goto commandError;
					break;

				case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT:
					if (enet_protocol_handle_send_unreliable_fragment (host, peer, command, & currentData))
						goto commandError;
					break;

				default:
					goto commandError;
				}

				if (peer != NULL &&
					(command -> header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0)
				{
					enet_uint16 sentTime;

					if (! (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME))
						break;

					sentTime = ENET_NET_TO_HOST_16 (header -> sentTime);

					switch (peer -> state)
					{
					case ENET_PEER_STATE_DISCONNECTING:
					case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT:
						break;

					case ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT:
						if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT)
							enet_peer_queue_acknowledgement (peer, command, sentTime);
						break;

					default:   
						enet_peer_queue_acknowledgement (peer, command, sentTime);        
						break;
					}
				}
			}

commandError:
			if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE)
				return 1;

			return 0;
	}

	static int
		enet_protocol_receive_incoming_commands (ENetHost * host, ENetEvent * event)
	{
		for (;;)
		{
			int receivedLength;
			ENetBuffer buffer;

			buffer.data = host -> packetData [0];
			buffer.dataLength = sizeof (host -> packetData [0]);

			receivedLength = enet_socket_receive (host -> socket,
				& host -> receivedAddress,
				& buffer,
				1);

			if (receivedLength < 0)
				return -1;

			if (receivedLength == 0)
				return 0;

			host -> receivedData = host -> packetData [0];
			host -> receivedDataLength = receivedLength;

			host -> totalReceivedData += receivedLength;
			host -> totalReceivedPackets ++;

			switch (enet_protocol_handle_incoming_commands (host, event))
			{
			case 1:
				return 1;

			case -1:
				return -1;

			default:
				break;
			}
		}

		return -1;
	}

	static void
		enet_protocol_send_acknowledgements (ENetHost * host, ENetPeer * peer)
	{
		ENetProtocol * command = & host -> commands [host -> commandCount];
		ENetBuffer * buffer = & host -> buffers [host -> bufferCount];
		ENetAcknowledgement * acknowledgement;
		ENetListIterator currentAcknowledgement;

		currentAcknowledgement = enet_list_begin (& peer -> acknowledgements);

		while (currentAcknowledgement != enet_list_end (& peer -> acknowledgements))
		{
			if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] ||
				buffer >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] ||
				peer -> mtu - host -> packetSize < sizeof (ENetProtocolAcknowledge))
			{
				host -> continueSending = 1;

				break;
			}

			acknowledgement = (ENetAcknowledgement *) currentAcknowledgement;

			currentAcknowledgement = enet_list_next (currentAcknowledgement);

			buffer -> data = command;
			buffer -> dataLength = sizeof (ENetProtocolAcknowledge);

			host -> packetSize += buffer -> dataLength;

			command -> header.command = ENET_PROTOCOL_COMMAND_ACKNOWLEDGE;
			command -> header.channelID = acknowledgement -> command.header.channelID;
			command -> acknowledge.receivedReliableSequenceNumber = ENET_HOST_TO_NET_16 (acknowledgement -> command.header.reliableSequenceNumber);
			command -> acknowledge.receivedSentTime = ENET_HOST_TO_NET_16 (acknowledgement -> sentTime);

			if ((acknowledgement -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT)
				enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE);

			enet_list_remove (& acknowledgement -> acknowledgementList);
			enet_free (acknowledgement);

			++ command;
			++ buffer;
		}

		host -> commandCount = command - host -> commands;
		host -> bufferCount = buffer - host -> buffers;
	}

	static void
		enet_protocol_send_unreliable_outgoing_commands (ENetHost * host, ENetPeer * peer)
	{
		ENetProtocol * command = & host -> commands [host -> commandCount];
		ENetBuffer * buffer = & host -> buffers [host -> bufferCount];
		ENetOutgoingCommand * outgoingCommand;
		ENetListIterator currentCommand;

		currentCommand = enet_list_begin (& peer -> outgoingUnreliableCommands);

		while (currentCommand != enet_list_end (& peer -> outgoingUnreliableCommands))
		{
			size_t commandSize;

			outgoingCommand = (ENetOutgoingCommand *) currentCommand;
			commandSize = commandSizes [outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK];

			if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] ||
				buffer + 1 >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] ||
				peer -> mtu - host -> packetSize < commandSize ||
				(outgoingCommand -> packet != NULL &&
				peer -> mtu - host -> packetSize < commandSize + outgoingCommand -> fragmentLength))
			{
				host -> continueSending = 1;

				break;
			}

			currentCommand = enet_list_next (currentCommand);

			if (outgoingCommand -> packet != NULL && outgoingCommand -> fragmentOffset == 0)
			{
				peer -> packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER;
				peer -> packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE;

				if (peer -> packetThrottleCounter > peer -> packetThrottle)
				{
					enet_uint16 reliableSequenceNumber = outgoingCommand -> reliableSequenceNumber,
						unreliableSequenceNumber = outgoingCommand -> unreliableSequenceNumber;
					for (;;)
					{
						-- outgoingCommand -> packet -> referenceCount;

						if (outgoingCommand -> packet -> referenceCount == 0)
							enet_packet_destroy (outgoingCommand -> packet);

						enet_list_remove (& outgoingCommand -> outgoingCommandList);
						enet_free (outgoingCommand);

						if (currentCommand == enet_list_end (& peer -> outgoingUnreliableCommands))
							break;

						outgoingCommand = (ENetOutgoingCommand *) currentCommand;
						if (outgoingCommand -> reliableSequenceNumber != reliableSequenceNumber ||
							outgoingCommand -> unreliableSequenceNumber != unreliableSequenceNumber)
							break;

						currentCommand = enet_list_next (currentCommand);
					}

					continue;
				}
			}

			buffer -> data = command;
			buffer -> dataLength = commandSize;

			host -> packetSize += buffer -> dataLength;

			* command = outgoingCommand -> command;

			enet_list_remove (& outgoingCommand -> outgoingCommandList);

			if (outgoingCommand -> packet != NULL)
			{
				++ buffer;

				buffer -> data = outgoingCommand -> packet -> data + outgoingCommand -> fragmentOffset;
				buffer -> dataLength = outgoingCommand -> fragmentLength;

				host -> packetSize += buffer -> dataLength;

				enet_list_insert (enet_list_end (& peer -> sentUnreliableCommands), outgoingCommand);
			}
			else
				enet_free (outgoingCommand);

			++ command;
			++ buffer;
		} 

		host -> commandCount = command - host -> commands;
		host -> bufferCount = buffer - host -> buffers;

		if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && 
			enet_list_empty (& peer -> outgoingReliableCommands) &&
			enet_list_empty (& peer -> outgoingUnreliableCommands) && 
			enet_list_empty (& peer -> sentReliableCommands))
			enet_peer_disconnect (peer, peer -> eventData);
	}

	static int
		enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event)
	{
		ENetOutgoingCommand * outgoingCommand;
		ENetListIterator currentCommand, insertPosition;

		currentCommand = enet_list_begin (& peer -> sentReliableCommands);
		insertPosition = enet_list_begin (& peer -> outgoingReliableCommands);

		while (currentCommand != enet_list_end (& peer -> sentReliableCommands))
		{
			outgoingCommand = (ENetOutgoingCommand *) currentCommand;

			currentCommand = enet_list_next (currentCommand);

			if (ENET_TIME_DIFFERENCE (host -> serviceTime, outgoingCommand -> sentTime) < outgoingCommand -> roundTripTimeout)
				continue;

			if (peer -> earliestTimeout == 0 ||
				ENET_TIME_LESS (outgoingCommand -> sentTime, peer -> earliestTimeout))
				peer -> earliestTimeout = outgoingCommand -> sentTime;

			if (peer -> earliestTimeout != 0 &&
				(ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= ENET_PEER_TIMEOUT_MAXIMUM ||
				(outgoingCommand -> roundTripTimeout >= outgoingCommand -> roundTripTimeoutLimit &&
				ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= ENET_PEER_TIMEOUT_MINIMUM)))
			{
				enet_protocol_notify_disconnect (host, peer, event);

				return 1;
			}

			if (outgoingCommand -> packet != NULL)
				peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength;

			++ peer -> packetsLost;

			outgoingCommand -> roundTripTimeout *= 2;

			enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));

			if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) &&
				! enet_list_empty (& peer -> sentReliableCommands))
			{
				outgoingCommand = (ENetOutgoingCommand *) currentCommand;

				peer -> nextTimeout = outgoingCommand -> sentTime + outgoingCommand -> roundTripTimeout;
			}
		}

		return 0;
	}

	static int
		enet_protocol_send_reliable_outgoing_commands (ENetHost * host, ENetPeer * peer)
	{
		ENetProtocol * command = & host -> commands [host -> commandCount];
		ENetBuffer * buffer = & host -> buffers [host -> bufferCount];
		ENetOutgoingCommand * outgoingCommand;
		ENetListIterator currentCommand;
		ENetChannel *channel;
		enet_uint16 reliableWindow;
		size_t commandSize;
		int windowExceeded = 0, windowWrap = 0, canPing = 1;

		currentCommand = enet_list_begin (& peer -> outgoingReliableCommands);

		while (currentCommand != enet_list_end (& peer -> outgoingReliableCommands))
		{
			outgoingCommand = (ENetOutgoingCommand *) currentCommand;

			channel = outgoingCommand -> command.header.channelID < peer -> channelCount ? & peer -> channels [outgoingCommand -> command.header.channelID] : NULL;
			reliableWindow = outgoingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
			if (channel != NULL)
			{
				if (! windowWrap &&      
					outgoingCommand -> sendAttempts < 1 && 
					! (outgoingCommand -> reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) &&
					(channel -> reliableWindows [(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE ||
					channel -> usedReliableWindows & ((((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) << reliableWindow) | 
					(((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) >> (ENET_PEER_RELIABLE_WINDOW_SIZE - reliableWindow)))))
					windowWrap = 1;
				if (windowWrap)
				{
					currentCommand = enet_list_next (currentCommand);

					continue;
				}
			}

			if (outgoingCommand -> packet != NULL)
			{
				if (! windowExceeded)
				{
					enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE;

					if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu))
						windowExceeded = 1;
				}
				if (windowExceeded)
				{
					currentCommand = enet_list_next (currentCommand);

					continue;
				}
			}

			canPing = 0;

			commandSize = commandSizes [outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK];
			if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] ||
				buffer + 1 >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] ||
				peer -> mtu - host -> packetSize < commandSize ||
				(outgoingCommand -> packet != NULL && 
				(enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength)))
			{
				host -> continueSending = 1;

				break;
			}

			currentCommand = enet_list_next (currentCommand);

			if (channel != NULL && outgoingCommand -> sendAttempts < 1)
			{
				channel -> usedReliableWindows |= 1 << reliableWindow;
				++ channel -> reliableWindows [reliableWindow];
			}

			++ outgoingCommand -> sendAttempts;

			if (outgoingCommand -> roundTripTimeout == 0)
			{
				outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance;
				outgoingCommand -> roundTripTimeoutLimit = ENET_PEER_TIMEOUT_LIMIT * outgoingCommand -> roundTripTimeout;
			}

			if (enet_list_empty (& peer -> sentReliableCommands))
				peer -> nextTimeout = host -> serviceTime + outgoingCommand -> roundTripTimeout;

			enet_list_insert (enet_list_end (& peer -> sentReliableCommands),
				enet_list_remove (& outgoingCommand -> outgoingCommandList));

			outgoingCommand -> sentTime = host -> serviceTime;

			buffer -> data = command;
			buffer -> dataLength = commandSize;

			host -> packetSize += buffer -> dataLength;
			host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME;

			* command = outgoingCommand -> command;

			if (outgoingCommand -> packet != NULL)
			{
				++ buffer;

				buffer -> data = outgoingCommand -> packet -> data + outgoingCommand -> fragmentOffset;
				buffer -> dataLength = outgoingCommand -> fragmentLength;

				host -> packetSize += outgoingCommand -> fragmentLength;

				peer -> reliableDataInTransit += outgoingCommand -> fragmentLength;
			}

			++ peer -> packetsSent;

			++ command;
			++ buffer;
		}

		host -> commandCount = command - host -> commands;
		host -> bufferCount = buffer - host -> buffers;

		return canPing;
	}

	static int
		enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int checkForTimeouts)
	{
		enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)];
		ENetProtocolHeader * header = (ENetProtocolHeader *) headerData;
		ENetPeer * currentPeer;
		int sentLength;
		size_t shouldCompress = 0;

		host -> continueSending = 1;

		while (host -> continueSending)
			for (host -> continueSending = 0,
				currentPeer = host -> peers;
				currentPeer < & host -> peers [host -> peerCount];
		++ currentPeer)
		{
			if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED ||
				currentPeer -> state == ENET_PEER_STATE_ZOMBIE)
				continue;

			host -> headerFlags = 0;
			host -> commandCount = 0;
			host -> bufferCount = 1;
			host -> packetSize = sizeof (ENetProtocolHeader);

			if (! enet_list_empty (& currentPeer -> acknowledgements))
				enet_protocol_send_acknowledgements (host, currentPeer);

			if (checkForTimeouts != 0 &&
				! enet_list_empty (& currentPeer -> sentReliableCommands) &&
				ENET_TIME_GREATER_EQUAL (host -> serviceTime, currentPeer -> nextTimeout) &&
				enet_protocol_check_timeouts (host, currentPeer, event) == 1)
				return 1;

			if ((enet_list_empty (& currentPeer -> outgoingReliableCommands) ||
				enet_protocol_send_reliable_outgoing_commands (host, currentPeer)) &&
				enet_list_empty (& currentPeer -> sentReliableCommands) &&
				ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= ENET_PEER_PING_INTERVAL &&
				currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing))
			{ 
				enet_peer_ping (currentPeer);
				enet_protocol_send_reliable_outgoing_commands (host, currentPeer);
			}

			if (! enet_list_empty (& currentPeer -> outgoingUnreliableCommands))
				enet_protocol_send_unreliable_outgoing_commands (host, currentPeer);

			if (host -> commandCount == 0)
				continue;

			if (currentPeer -> packetLossEpoch == 0)
				currentPeer -> packetLossEpoch = host -> serviceTime;
			else
				if (ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> packetLossEpoch) >= ENET_PEER_PACKET_LOSS_INTERVAL &&
					currentPeer -> packetsSent > 0)
				{
					enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent;

#ifdef ENET_DEBUG
#ifdef WIN32
					printf (
#else
					fprintf (stderr, 
#endif
						"peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u/%u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingReliableCommands), enet_list_size (& currentPeer -> outgoingUnreliableCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0);
#endif

					currentPeer -> packetLossVariance -= currentPeer -> packetLossVariance / 4;

					if (packetLoss >= currentPeer -> packetLoss)
					{
						currentPeer -> packetLoss += (packetLoss - currentPeer -> packetLoss) / 8;
						currentPeer -> packetLossVariance += (packetLoss - currentPeer -> packetLoss) / 4;
					}
					else
					{
						currentPeer -> packetLoss -= (currentPeer -> packetLoss - packetLoss) / 8;
						currentPeer -> packetLossVariance += (currentPeer -> packetLoss - packetLoss) / 4;
					}

					currentPeer -> packetLossEpoch = host -> serviceTime;
					currentPeer -> packetsSent = 0;
					currentPeer -> packetsLost = 0;
				}

				host -> buffers -> data = headerData;
				if (host -> headerFlags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME)
				{
					header -> sentTime = ENET_HOST_TO_NET_16 (host -> serviceTime & 0xFFFF);

					host -> buffers -> dataLength = sizeof (ENetProtocolHeader);
				}
				else
					host -> buffers -> dataLength = (size_t) & ((ENetProtocolHeader *) 0) -> sentTime;

				shouldCompress = 0;
				if (host -> compressor.context != NULL && host -> compressor.compress != NULL)
				{
					size_t originalSize = host -> packetSize - sizeof(ENetProtocolHeader),
						compressedSize = host -> compressor.compress (host -> compressor.context,
						& host -> buffers [1], host -> bufferCount - 1,
						originalSize,
						host -> packetData [1],
						originalSize);
					if (compressedSize > 0 && compressedSize < originalSize)
					{
						host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED;
						shouldCompress = compressedSize;
#ifdef ENET_DEBUG_COMPRESS
#ifdef WIN32
						printf (
#else
						fprintf (stderr,
#endif
							"peer %u: compressed %u -> %u (%u%%)\n", currentPeer -> incomingPeerID, originalSize, compressedSize, (compressedSize * 100) / originalSize);
#endif
					}
				}

				if (currentPeer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID)
					host -> headerFlags |= currentPeer -> outgoingSessionID << ENET_PROTOCOL_HEADER_SESSION_SHIFT;
				header -> peerID = ENET_HOST_TO_NET_16 (currentPeer -> outgoingPeerID | host -> headerFlags);
				if (host -> checksum != NULL)
				{
					enet_uint32 * checksum = (enet_uint32 *) & headerData [host -> buffers -> dataLength];
					* checksum = currentPeer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID ? currentPeer -> connectID : 0;
					host -> buffers -> dataLength += sizeof (enet_uint32);
					* checksum = host -> checksum (host -> buffers, host -> bufferCount);
				}

				if (shouldCompress > 0)
				{
					host -> buffers [1].data = host -> packetData [1];
					host -> buffers [1].dataLength = shouldCompress;
					host -> bufferCount = 2;
				}

				currentPeer -> lastSendTime = host -> serviceTime;

				sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount);

				enet_protocol_remove_sent_unreliable_commands (currentPeer);

				if (sentLength < 0)
					return -1;

				host -> totalSentData += sentLength;
				host -> totalSentPackets ++;
		}

		return 0;
	}

	/** Sends any queued packets on the host specified to its designated peers.

	@param host   host to flush
	@remarks this function need only be used in circumstances where one wishes to send queued packets earlier than in a call to enet_host_service().
	@ingroup host
	*/
	void
		enet_host_flush (ENetHost * host)
	{
		host -> serviceTime = enet_time_get ();

		enet_protocol_send_outgoing_commands (host, NULL, 0);
	}

	/** Checks for any queued events on the host and dispatches one if available.

	@param host    host to check for events
	@param event   an event structure where event details will be placed if available
	@retval > 0 if an event was dispatched
	@retval 0 if no events are available
	@retval < 0 on failure
	@ingroup host
	*/
	int
		enet_host_check_events (ENetHost * host, ENetEvent * event)
	{
		if (event == NULL) return -1;

		event -> type = ENET_EVENT_TYPE_NONE;
		event -> peer = NULL;
		event -> packet = NULL;

		return enet_protocol_dispatch_incoming_commands (host, event);
	}

	/** Waits for events on the host specified and shuttles packets between
	the host and its peers.

	@param host    host to service
	@param event   an event structure where event details will be placed if one occurs
	if event == NULL then no events will be delivered
	@param timeout number of milliseconds that ENet should wait for events
	@retval > 0 if an event occurred within the specified time limit
	@retval 0 if no event occurred
	@retval < 0 on failure
	@remarks enet_host_service should be called fairly regularly for adequate performance
	@ingroup host
	*/
	int
		enet_host_service (ENetHost * host, ENetEvent * event, enet_uint32 timeout)
	{
		enet_uint32 waitCondition;

		if (event != NULL)
		{
			event -> type = ENET_EVENT_TYPE_NONE;
			event -> peer = NULL;
			event -> packet = NULL;

			switch (enet_protocol_dispatch_incoming_commands (host, event))
			{
			case 1:
				return 1;

			case -1:
				perror ("Error dispatching incoming packets");

				return -1;

			default:
				break;
			}
		}

		host -> serviceTime = enet_time_get ();

		timeout += host -> serviceTime;

		do
		{
			if (ENET_TIME_DIFFERENCE (host -> serviceTime, host -> bandwidthThrottleEpoch) >= ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL)
				enet_host_bandwidth_throttle (host);

			switch (enet_protocol_send_outgoing_commands (host, event, 1))
			{
			case 1:
				return 1;

			case -1:
				perror ("Error sending outgoing packets");

				return -1;

			default:
				break;
			}

			switch (enet_protocol_receive_incoming_commands (host, event))
			{
			case 1:
				return 1;

			case -1:
				perror ("Error receiving incoming packets");

				return -1;

			default:
				break;
			}

			switch (enet_protocol_send_outgoing_commands (host, event, 1))
			{
			case 1:
				return 1;

			case -1:
				perror ("Error sending outgoing packets");

				return -1;

			default:
				break;
			}

			if (event != NULL)
			{
				switch (enet_protocol_dispatch_incoming_commands (host, event))
				{
				case 1:
					return 1;

				case -1:
					perror ("Error dispatching incoming packets");

					return -1;

				default:
					break;
				}
			}

			host -> serviceTime = enet_time_get ();

			if (ENET_TIME_GREATER_EQUAL (host -> serviceTime, timeout))
				return 0;

			waitCondition = ENET_SOCKET_WAIT_RECEIVE;

			if (enet_socket_wait (host -> socket, & waitCondition, ENET_TIME_DIFFERENCE (timeout, host -> serviceTime)) != 0)
				return -1;

			host -> serviceTime = enet_time_get ();
		} while (waitCondition == ENET_SOCKET_WAIT_RECEIVE);

		return 0; 
	}


	static enet_uint32 timeBase = 0;

	int
		enet_initialize (void)
	{
		WORD versionRequested = MAKEWORD (1, 1);
		WSADATA wsaData;

		if (WSAStartup (versionRequested, & wsaData))
			return -1;

		if (LOBYTE (wsaData.wVersion) != 1||
			HIBYTE (wsaData.wVersion) != 1)
		{
			WSACleanup ();

			return -1;
		}

		timeBeginPeriod (1);

		return 0;
	}

	void
		enet_deinitialize (void)
	{
		timeEndPeriod (1);

		WSACleanup ();
	}

	enet_uint32
		enet_time_get (void)
	{
		return (enet_uint32) timeGetTime () - timeBase;
	}

	void
		enet_time_set (enet_uint32 newTimeBase)
	{
		timeBase = (enet_uint32) timeGetTime () - newTimeBase;
	}

	int
		enet_address_set_host (ENetAddress * address, const char * name)
	{
		struct hostent * hostEntry;

		hostEntry = gethostbyname (name);
		if (hostEntry == NULL ||
			hostEntry -> h_addrtype != AF_INET)
		{
			unsigned long host = inet_addr (name);
			if (host == INADDR_NONE)
				return -1;
			address -> host = host;
			return 0;
		}

		address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0];

		return 0;
	}

	int
		enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength)
	{
		char * addr = inet_ntoa (* (struct in_addr *) & address -> host);
		if (addr == NULL)
			return -1;
		strncpy (name, addr, nameLength);
		return 0;
	}

	int
		enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength)
	{
		struct in_addr in;
		struct hostent * hostEntry;

		in.s_addr = address -> host;

		hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET);
		if (hostEntry == NULL)
			return enet_address_get_host_ip (address, name, nameLength);

		strncpy (name, hostEntry -> h_name, nameLength);

		return 0;
	}

	int
		enet_socket_bind (ENetSocket socket, const ENetAddress * address)
	{
		struct sockaddr_in sin;

		memset (& sin, 0, sizeof (struct sockaddr_in));

		sin.sin_family = AF_INET;

		if (address != NULL)
		{
			sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
			sin.sin_addr.s_addr = address -> host;
		}
		else
		{
			sin.sin_port = 0;
			sin.sin_addr.s_addr = INADDR_ANY;
		}

		return bind (socket,
			(struct sockaddr *) & sin,
			sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0;
	}

	int
		enet_socket_listen (ENetSocket socket, int backlog)
	{
		return listen (socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0;
	}

	ENetSocket
		enet_socket_create (ENetSocketType type)
	{
		return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
	}

	int
		enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value)
	{
		int result = SOCKET_ERROR;
		switch (option)
		{
		case ENET_SOCKOPT_NONBLOCK:
			{
				u_long nonBlocking = (u_long) value;
				result = ioctlsocket (socket, FIONBIO, & nonBlocking);
				break;
			}

		case ENET_SOCKOPT_BROADCAST:
			result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int));
			break;

		case ENET_SOCKOPT_REUSEADDR:
			result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int));
			break;

		case ENET_SOCKOPT_RCVBUF:
			result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int));
			break;

		case ENET_SOCKOPT_SNDBUF:
			result = setsockopt (socket, SOL_SOCKET, SO_SNDBUF, (char *) & value, sizeof (int));
			break;

		default:
			break;
		}
		return result == SOCKET_ERROR ? -1 : 0;
	}

	int
		enet_socket_connect (ENetSocket socket, const ENetAddress * address)
	{
		struct sockaddr_in sin;

		memset (& sin, 0, sizeof (struct sockaddr_in));

		sin.sin_family = AF_INET;
		sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
		sin.sin_addr.s_addr = address -> host;

		return connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0;
	}

	ENetSocket
		enet_socket_accept (ENetSocket socket, ENetAddress * address)
	{
		SOCKET result;
		struct sockaddr_in sin;
		int sinLength = sizeof (struct sockaddr_in);

		result = accept (socket, 
			address != NULL ? (struct sockaddr *) & sin : NULL, 
			address != NULL ? & sinLength : NULL);

		if (result == INVALID_SOCKET)
			return ENET_SOCKET_NULL;

		if (address != NULL)
		{
			address -> host = (enet_uint32) sin.sin_addr.s_addr;
			address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
		}

		return result;
	}

	void
		enet_socket_destroy (ENetSocket socket)
	{
		closesocket (socket);
	}

	int
		enet_socket_send (ENetSocket socket,
		const ENetAddress * address,
		const ENetBuffer * buffers,
		size_t bufferCount)
	{
		struct sockaddr_in sin;
		DWORD sentLength;

		if (address != NULL)
		{
			memset (& sin, 0, sizeof (struct sockaddr_in));

			sin.sin_family = AF_INET;
			sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
			sin.sin_addr.s_addr = address -> host;
		}

		if (WSASendTo (socket, 
			(LPWSABUF) buffers,
			(DWORD) bufferCount,
			& sentLength,
			0,
			address != NULL ? (struct sockaddr *) & sin : 0,
			address != NULL ? sizeof (struct sockaddr_in) : 0,
			NULL,
			NULL) == SOCKET_ERROR)
		{
			if (WSAGetLastError () == WSAEWOULDBLOCK)
				return 0;

			return -1;
		}

		return (int) sentLength;
	}

	int
		enet_socket_receive (ENetSocket socket,
		ENetAddress * address,
		ENetBuffer * buffers,
		size_t bufferCount)
	{
		INT sinLength = sizeof (struct sockaddr_in);
		DWORD flags = 0,
			recvLength;
		struct sockaddr_in sin;

		if (WSARecvFrom (socket,
			(LPWSABUF) buffers,
			(DWORD) bufferCount,
			& recvLength,
			& flags,
			address != NULL ? (struct sockaddr *) & sin : NULL,
			address != NULL ? & sinLength : NULL,
			NULL,
			NULL) == SOCKET_ERROR)
		{
			switch (WSAGetLastError ())
			{
			case WSAEWOULDBLOCK:
			case WSAECONNRESET:
				return 0;
			}

			return -1;
		}

		if (flags & MSG_PARTIAL)
			return -1;

		if (address != NULL)
		{
			address -> host = (enet_uint32) sin.sin_addr.s_addr;
			address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
		}

		return (int) recvLength;
	}

	int
		enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout)
	{
		struct timeval timeVal;

		timeVal.tv_sec = timeout / 1000;
		timeVal.tv_usec = (timeout % 1000) * 1000;

		return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal);
	}

	int
		enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout)
	{
		fd_set readSet, writeSet;
		struct timeval timeVal;
		int selectCount;

		timeVal.tv_sec = timeout / 1000;
		timeVal.tv_usec = (timeout % 1000) * 1000;

		FD_ZERO (& readSet);
		FD_ZERO (& writeSet);

		if (* condition & ENET_SOCKET_WAIT_SEND)
			FD_SET (socket, & writeSet);

		if (* condition & ENET_SOCKET_WAIT_RECEIVE)
			FD_SET (socket, & readSet);

		selectCount = select (socket + 1, & readSet, & writeSet, NULL, & timeVal);

		if (selectCount < 0)
			return -1;

		* condition = ENET_SOCKET_WAIT_NONE;

		if (selectCount == 0)
			return 0;

		if (FD_ISSET (socket, & writeSet))
			* condition |= ENET_SOCKET_WAIT_SEND;

		if (FD_ISSET (socket, & readSet))
			* condition |= ENET_SOCKET_WAIT_RECEIVE;

		return 0;
	} 
}